chromium/content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_browsertest.cc

// Copyright 2023 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/renderer_host/navigation_transitions/navigation_entry_screenshot.h"

#include <string_view>
#include <vector>

#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/test_future.h"
#include "base/test/test_simple_task_runner.h"
#include "cc/test/pixel_test_utils.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "content/browser/browser_context_impl.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_manager.h"
#include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/navigation_transition_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/web_ui_browsertest_util.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/viz/privileged/mojom/compositing/features.mojom-features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/android/view_android.h"
#include "ui/gfx/switches.h"
#include "url/url_constants.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif

namespace content {

namespace {

using CacheHitOrMissReason = NavigationTransitionData::CacheHitOrMissReason;

// A test-only alternative to
// `NavigationEntryScreenshotCache::RemoveScreenshot` that does not evict the
// cached screenshot.
NavigationEntryScreenshot* PreviewScreenshotForEntry(NavigationEntry* entry) {
  EXPECT_TRUE(entry);
  auto* data = entry->GetUserData(NavigationEntryScreenshot::kUserDataKey);
  if (!data) {
    return nullptr;
  }
  return static_cast<NavigationEntryScreenshot*>(data);
}

// Navigates the current tab to `destination`, and:
// - Makes sure the current tab is screenshotted, and the screenshot is stored
//   inside the correct `NavigationEntry`.
// - Makes sure `destination` has reached a steady state in the renderer, so
//   that its surface can be copied.
void NavigateTabAndWaitForScreenshotCached(WebContents* tab,
                                           NavigationControllerImpl& controller,
                                           const GURL& destination,
                                           bool same_doc_nav = false) {
  const int num_request_before_nav =
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting();
  const int entries_count_before_nav = controller.GetEntryCount();
  ScopedScreenshotCapturedObserverForTesting observer(
      controller.GetLastCommittedEntryIndex());
  ASSERT_TRUE(NavigateToURL(tab, destination));
  // We don't need to wait for the same-doc navigations. When the browser
  // receives the screenshot for same-doc navigations, the renderer must have
  // submitted a new frame. Using the observer on screenshot capture is enough.
  if (!same_doc_nav) {
    WaitForCopyableViewInWebContents(tab);
  }
  observer.Wait();
  ASSERT_EQ(controller.GetEntryCount(), entries_count_before_nav + 1);
  ASSERT_EQ(
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(),
      num_request_before_nav + 1);
}

// Identical functionalities as `NavigateTabAndWaitForScreenshotCached`, except
// for a history navigation.
void HistoryNavigateTabAndWaitForScreenshotCached(
    WebContents* tab,
    NavigationControllerImpl& controller,
    int offset,
    bool same_doc_nav = false) {
  const int num_request_before_nav =
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting();
  const int entries_count_before_nav = controller.GetEntryCount();
  ScopedScreenshotCapturedObserverForTesting observer(
      controller.GetLastCommittedEntryIndex());
  ASSERT_TRUE(HistoryGoToOffset(tab, offset));
  if (!same_doc_nav) {
    WaitForCopyableViewInWebContents(tab);
  }
  observer.Wait();
  ASSERT_EQ(controller.GetEntryCount(), entries_count_before_nav);
  ASSERT_EQ(
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(),
      num_request_before_nav + 1);
}

struct ScreenshotCaptureTestNavigationType {
  bool same_origin;
  bool enable_bfcache;
};

std::string DescribeBFCacheType(
    const ::testing::TestParamInfo<ScreenshotCaptureTestNavigationType>& info) {
  if (info.param.enable_bfcache) {
    return "BFCacheEnabled";
  } else {
    return "BFCacheDisabled";
  }
}

std::string DescribeNavOriginType(
    const ::testing::TestParamInfo<ScreenshotCaptureTestNavigationType>& info) {
  if (info.param.same_origin) {
    return "SameOrigin";
  } else {
    return "CrossOrigin";
  }
}

std::string DescribeNavType(
    const ::testing::TestParamInfo<ScreenshotCaptureTestNavigationType>& info) {
  return DescribeNavOriginType(info) + "_" + DescribeBFCacheType(info);
}

constexpr ScreenshotCaptureTestNavigationType kNavTypes[] = {
    ScreenshotCaptureTestNavigationType{.same_origin = true,
                                        .enable_bfcache = true},
    ScreenshotCaptureTestNavigationType{.same_origin = true,
                                        .enable_bfcache = false},
    ScreenshotCaptureTestNavigationType{.same_origin = false,
                                        .enable_bfcache = true},
    ScreenshotCaptureTestNavigationType{.same_origin = false,
                                        .enable_bfcache = false},
};

class HostGetter {
 public:
  virtual ~HostGetter() = default;
  virtual std::string Get() = 0;

 protected:
  const std::array<std::string, 2> hosts = {"a.com", "b.com"};
};

class HostGetterSameOrigin : public HostGetter {
 public:
  ~HostGetterSameOrigin() override = default;

  // Always returns "a.com";
  std::string Get() override { return hosts[0]; }
};

class HostGetterCrossOrigin : public HostGetter {
 public:
  ~HostGetterCrossOrigin() override = default;

  // Alternatingly returns "a.com" / "b.com", such that each navigation is
  // cross-origin.
  std::string Get() override {
    index_ ^= 1;
    return hosts[index_];
  }

 private:
  int index_ = 1;
};

}  // namespace

class NavigationEntryScreenshotBrowserTestBase : public ContentBrowserTest {
 public:
  NavigationEntryScreenshotBrowserTestBase() = default;
  ~NavigationEntryScreenshotBrowserTestBase() override = default;

  void SetUp() override {
    NavigationTransitionUtils::ResetNumCopyOutputRequestIssuedForTesting();
    ContentBrowserTest::SetUp();
  }

  void TearDown() override {
    NavigationTransitionUtils::ResetNumCopyOutputRequestIssuedForTesting();
    ContentBrowserTest::TearDown();
  }

  virtual bool EnableCompression() const { return false; }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ContentBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kForcePrefersNoReducedMotion);
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();

    if (!EnableCompression()) {
      NavigationEntryScreenshot::SetDisableCompressionForTesting(true);
    }

    ASSERT_TRUE(
        base::FeatureList::IsEnabled(blink::features::kBackForwardTransitions));

    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->ServeFilesFromSourceDirectory(
        GetTestDataFilePath());
    net::test_server::RegisterDefaultHandlers(embedded_test_server());
    SetupCrossSiteRedirector(embedded_test_server());

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

    // Explicitly limit the output size as 10% of the logical viewport size.
    // This prevents the potential out-of-memory issue during the browsertest.
    // OoM causes the screenshots to be purged from the cache, failing the
    // tests.
    NavigationTransitionUtils::SetCapturedScreenshotSizeForTesting(
        GetScaledViewportSize());
  }

  static void ExpectBitmapRowsAreColor(
      const SkBitmap& bitmap,
      SkColor color,
      std::optional<gfx::Rect> compare_region = std::nullopt) {
    int num_pixel_mismatch = 0;
    gfx::Rect err_bounding_box;

    int row_start = 0;
    int row_end = bitmap.height();
    int col_start = 0;
    int col_end = bitmap.width();

    if (compare_region.has_value()) {
      row_start = compare_region->y();
      row_end = compare_region->bottom();
      col_start = compare_region->x();
      col_end = compare_region->right();
    }

    for (int r = row_start; r < row_end; ++r) {
      for (int c = col_start; c < col_end; ++c) {
        if (bitmap.getColor(c, r) != color) {
          ++num_pixel_mismatch;
          err_bounding_box.Union(gfx::Rect(c, r, 1, 1));
        }
      }
    }
    if (num_pixel_mismatch != 0) {
      EXPECT_TRUE(false)
          << "Number of pixel mismatches: " << num_pixel_mismatch
          << "; error bounding box: " << err_bounding_box.ToString()
          << "; bitmap size: "
          << gfx::Size(bitmap.width(), bitmap.height()).ToString()
          << "; expect color " << base::StringPrintf("%x", color)
          << "; actual bitmap " << cc::GetPNGDataUrl(bitmap);
    }
  }

  void ExpectScreenshotIsColor(
      NavigationEntryScreenshot* screenshot,
      SkColor color,
      std::optional<gfx::Rect> compare_region = std::nullopt) {
    ASSERT_FALSE(EnableCompression());
    EXPECT_NE(screenshot, nullptr);
    EXPECT_EQ(screenshot->dimensions_without_compression(),
              GetScaledViewportSize());

    auto bitmap = screenshot->GetBitmapForTesting();
    ExpectBitmapRowsAreColor(bitmap, color, compare_region);
  }

  void AssertOrderedScreenshotsAre(
      NavigationControllerImpl& controller,
      const std::vector<std::optional<SkColor>>& expected_screenshots,
      std::optional<gfx::Rect> compare_region = std::nullopt) {
    ASSERT_EQ(controller.GetEntryCount(),
              static_cast<int>(expected_screenshots.size()));
    for (int index = 0; index < controller.GetEntryCount(); ++index) {
      auto* entry = controller.GetEntryAtIndex(index);
      if (expected_screenshots[index].has_value()) {
        auto* screenshot = PreviewScreenshotForEntry(entry);
        if (EnableCompression()) {
          EXPECT_NE(screenshot, nullptr);
        } else {
          ExpectScreenshotIsColor(
              screenshot, expected_screenshots[index].value(), compare_region);
        }
      } else {
        EXPECT_EQ(PreviewScreenshotForEntry(entry), nullptr);
      }
    }
  }

  void AssertCacheHitOrMissReasonsAre(
      NavigationControllerImpl& controller,
      const std::vector<std::optional<CacheHitOrMissReason>>&
          expected_reasons) {
    ASSERT_EQ(controller.GetEntryCount(),
              static_cast<int>(expected_reasons.size()));
    for (int index = 0; index < controller.GetEntryCount(); ++index) {
      auto* entry = controller.GetEntryAtIndex(index);
      EXPECT_EQ(entry->navigation_transition_data().cache_hit_or_miss_reason(),
                expected_reasons[index]);
    }
  }

  gfx::Size GetScaledViewportSize() {
    // Scale down the size to avoid memory pressure causing cache purging.
    return ScaleToRoundedSize(
        web_contents()->GetNativeView()->GetPhysicalBackingSize(),
        /*scale=*/0.1);
  }

  size_t GetUncompressedScreenshotSizeInBytes() {
    // 4 bytes per pixel.
    return 4 * GetScaledViewportSize().Area64();
  }

  NavigationEntryScreenshotManager* GetManagerForTab(WebContents* tab) {
    return BrowserContextImpl::From(tab->GetBrowserContext())
        ->GetNavigationEntryScreenshotManager();
  }

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

class NavigationEntryScreenshotBrowserTest
    : public NavigationEntryScreenshotBrowserTestBase,
      public ::testing::WithParamInterface<
          ScreenshotCaptureTestNavigationType> {
 public:
  NavigationEntryScreenshotBrowserTest() = default;
  ~NavigationEntryScreenshotBrowserTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    base::FieldTrialParams bf_transition_params;
    if (Use1MinuteEvictionDelay()) {
      bf_transition_params = {{"invisible-cache-cleanup-delay", "1m"}};
    }
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {blink::features::kBackForwardTransitions, bf_transition_params}};

    if (GetParam().enable_bfcache) {
      scoped_feature_list_.InitWithFeaturesAndParameters(
          GetDefaultEnabledBackForwardCacheFeaturesForTesting(enabled_features),
          GetDefaultDisabledBackForwardCacheFeaturesForTesting());
    } else {
      scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
      command_line->AppendSwitch(switches::kDisableBackForwardCache);
    }

    if (GetParam().same_origin) {
      host_getter_ = std::make_unique<HostGetterSameOrigin>();
    } else {
      host_getter_ = std::make_unique<HostGetterCrossOrigin>();
    }

    NavigationEntryScreenshotBrowserTestBase::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    NavigationEntryScreenshotBrowserTestBase::SetUpOnMainThread();

    // The default WebContents has only the initial navigation entry. This
    // WebContents does not have a RWHV associated with it, making
    // `GetScaledViewportSize` impossible. For this reason we manually load a
    // "red" document.
    ASSERT_TRUE(web_contents()
                    ->GetController()
                    .GetLastCommittedEntry()
                    ->IsInitialEntry());
    ASSERT_TRUE(NavigateToURL(web_contents(), GetNextUrl("/red.html")));
    WaitForCopyableViewInWebContents(web_contents());

    ASSERT_FALSE(web_contents()
                     ->GetController()
                     .GetLastCommittedEntry()
                     ->IsInitialEntry());
    // We don't capture any screenshot for the initial navigation entry (which
    // is replaced by the "red" entry).
    ASSERT_EQ(PreviewScreenshotForEntry(
                  web_contents()->GetController().GetLastCommittedEntry()),
              nullptr);
    ASSERT_EQ(GetManagerForTab(web_contents())->GetCurrentCacheSize(), 0U);

    ASSERT_TRUE(web_contents()->GetRenderWidgetHostView());
  }

  virtual bool Use1MinuteEvictionDelay() const { return false; }

  std::string GetNextHost() { return host_getter_->Get(); }

  GURL GetNextUrl(std::string_view path) {
    return embedded_test_server()->GetURL(GetNextHost(), path);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;

  std::unique_ptr<HostGetter> host_getter_;
};

// Test the caching, retrieving and eviction of the
// `NavigationEntryScreenshotCache`, within a single tab, with both non-history
// navigation and history navigation.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       PrimaryMainFrameNav) {
// TODO(crbug.com/353908058): Re-enable this test for automotive.
#if BUILDFLAG(IS_ANDROID)
  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
    GTEST_SKIP() << "This test is flaky on automotive. crbug.com/353908058";
  }
#endif  // BUILDFLAG(IS_ANDROID)
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green&, blue*] -> [red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/red.html"));
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red&, green&, blue&, red*] -> [red, green&, blue&, red&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(
        controller,
        {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green&, blue&, red&, green*] -> "
        "[red, green, blue&, red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, SK_ColorRED,
                     SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green, blue&, red&, green&, blue*] -> "
        "[red, green, blue&, red&, green*, blue&]");
    {
      // This simulates:
      // - The destination screenshot is first removed from the cache and used
      //   for preview. This is mimicking the behaviour during a swipe animation
      //   where the preview is removed from the cache before navigating.
      // - Then the navigation starts and commits, and a new screenshot is
      //   cached for the origin page.
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorGREEN);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, SK_ColorRED,
                     std::nullopt, SK_ColorBLUE});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green, blue&, red&, green*, blue&] -> "
        "[red, green, blue&, red*, green&, blue&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorRED);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, std::nullopt,
                     SK_ColorGREEN, SK_ColorBLUE});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green, blue&, red*, green&, blue&] -> "
        "[red, green, blue*, red&, green&, blue&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorBLUE);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, std::nullopt, SK_ColorRED,
                     SK_ColorGREEN, SK_ColorBLUE});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green, blue*, red&, green&, blue&] -> "
        "[red, green*, blue&, red&, green&, blue]");

    // No screenshots for the "green" entry to the left of the "blue*" entry.
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, SK_ColorRED,
                     SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
}

// Testing the back/forward history navigations that span multiple navigation
// entries.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest, MultipleEntries) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/red.html"));
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }

  // History back nav to the first entry (red).
  {
    SCOPED_TRACE("[red&, green&, blue&, red*] -> [red*, green&, blue&, red&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-3));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorRED);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -3);
    ASSERT_EQ(controller.GetEntryCount(), 4);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }

  // History forward nav to the third entry (blue).
  {
    SCOPED_TRACE("[red*, green&, blue&, red&] -> [red&, green&, blue*, red&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(2));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorBLUE);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller, 2);
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, std::nullopt, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
}

// Testing the cache's behavior if the destination navigation already has a
// screenshot. This can happen if the user performs history navigation without
// gesture (e.g., via the back button).
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       WithoutRemovingScreenshotFromDestinationEntry) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(3 * page_size);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/red.html"));
    ASSERT_EQ(controller.GetEntryCount(), 4);
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green&, blue&, red*] -> [red*, green&, blue&, red&]");
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -3);
    ASSERT_EQ(controller.GetEntryCount(), 4);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
  {
    SCOPED_TRACE("[red*, green&, blue&, red&] -> [red&, green&, blue*, red&]");
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller, 2);
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, std::nullopt, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
}

// Testing the screenshots are captured / evicted properly with multiple tabs.
// These tabs are within the same profile.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest, MultipleTabs) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("tab1: [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }

  // Creates a second tab within the same profile such that two tabs share the
  // same manager. `NavigationEntryScreenshotManager` is per Profile
  // (`BrowserContext`) - it budgets the memories for all the screenshots across
  // different tabs.
  auto* shell2 = Shell::CreateNewWindow(
      shell()->web_contents()->GetBrowserContext(), GetNextUrl("/red.html"),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab2 = static_cast<WebContentsImpl*>(shell2->web_contents());
  ASSERT_EQ(manager, GetManagerForTab(tab2));
  ASSERT_TRUE(tab2->GetController().GetLastCommittedEntry()->IsInitialEntry());
  WaitForCopyableViewInWebContents(tab2);
  // We don't capture for the initial entry.
  ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  auto& controller2 = tab2->GetController();
  ASSERT_EQ(controller2.GetEntryCount(), 1);

  {
    SCOPED_TRACE(
        "tab1: [red&, green&, blue*] -> [red&, green&, blue*] (no change); "
        "tab2: [red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(controller2, {SK_ColorRED, std::nullopt});
    // No change in tab1, because we have one cache slot for a new screenshot in
    // tab2.
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "tab1: [red&, green&, blue*] -> [red, green&, blue*]; "
        "tab2: [red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller2,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    // Tab1's "red" screenshot is evicted. We always evict from the least
    // recently used tab (tab1), and always evict from the most distant
    // navigation entry (red).
    AssertOrderedScreenshotsAre(controller,
                                {std::nullopt, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "tab1: [red, green&, blue*] -> [red, green, blue*]; "
        "tab2: [red&, green&, blue*] -> [red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/red.html"));
    AssertOrderedScreenshotsAre(
        controller2, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    // Tab1's "green" is evicted.
    AssertOrderedScreenshotsAre(controller,
                                {std::nullopt, std::nullopt, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "tab1: [red, green, blue*] -> [red, green*, blue&]; "
        "tab2: [red&, green&, blue&, red*] -> [red, green&, blue&, red*]");
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(controller,
                                {std::nullopt, std::nullopt, SK_ColorBLUE});
    // Screenshot for "red" of tab2 is evicted.
    AssertOrderedScreenshotsAre(
        controller2, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "tab1: [red, green*, blue&] -> [red*, green&, blue&]; "
        "tab2: [red, green&, blue&, red*] -> [red, green, blue&, red*]");
    // History-navigate tab1.
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(controller,
                                {std::nullopt, SK_ColorGREEN, SK_ColorBLUE});
    // Screenshot for "red" is evicted.
    AssertOrderedScreenshotsAre(
        controller2, {std::nullopt, std::nullopt, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }

  // Close tab2.
  tab2->Close();
  ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);

  // Clear the navigation entries of tab1.
  controller.PruneAllButLastCommitted();
  ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
}

// Testing the screenshots are captured / evicted properly with multiple tabs
// from different profiles.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest, MultipleProfiles) {
  // Max of two screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 2 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("tab1: [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }

  // Creates a second tab but of a different profile, such that each tab is
  // managed independently.
  auto* shell2 = Shell::CreateNewWindow(
      ShellContentBrowserClient::Get()->off_the_record_browser_context(),
      GetNextUrl("/red.html"),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab2 = static_cast<WebContentsImpl*>(shell2->web_contents());
  ASSERT_TRUE(tab2->GetController().GetLastCommittedEntry()->IsInitialEntry());
  WaitForCopyableViewInWebContents(tab2);
  auto* manager2 = GetManagerForTab(tab2);
  ASSERT_NE(manager, manager2);
  // We don't capture for the initial entry.
  ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  ASSERT_EQ(manager2->GetCurrentCacheSize(), 0U);
  auto& controller2 = tab2->GetController();
  ASSERT_EQ(controller2.GetEntryCount(), 1);

  {
    SCOPED_TRACE(
        "tab1: [red&, green&, blue*]->[red&, green&, blue*] (no change);"
        "tab2: [red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/green.html"));
    ASSERT_EQ(controller.GetEntryCount(), 3);
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    AssertOrderedScreenshotsAre(controller2, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
    ASSERT_EQ(manager2->GetCurrentCacheSize(), page_size);
  }
  {
    // tab1: [red&, green&, blue*] -> [red&, green&, blue*] (no change)
    // tab2: [red&, green*] -> [red&, green&, blue*]
    SCOPED_TRACE(
        "tab1: [red&, green&, blue*]->[red&, green&, blue*] (no change)"
        "tab2: [red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/blue.html"));
    // Caching a new screenshot for tab2 has no impact on tab1 since they are
    // managed independently.
    ASSERT_EQ(controller.GetEntryCount(), 3);
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    AssertOrderedScreenshotsAre(controller2,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
    ASSERT_EQ(manager2->GetCurrentCacheSize(), 2 * page_size);
  }

  // Close tab2.
  tab2->Close();
  ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  ASSERT_EQ(manager2->GetCurrentCacheSize(), 0U);

  // Clear entries from tab1.
  controller.PruneAllButLastCommitted();
  ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
  ASSERT_EQ(manager2->GetCurrentCacheSize(), 0U);
}

// Screenshots are captured for renderer-initiated navigations (e.g.,
// link-clicking).
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       RendererInitiatedNav) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  SCOPED_TRACE("[red*] -> [red&, green*]");
  ScopedScreenshotCapturedObserverForTesting observer(
      web_contents()->GetController().GetLastCommittedEntryIndex());
  ASSERT_TRUE(
      NavigateToURLFromRenderer(web_contents(), GetNextUrl("/green.html")));
  WaitForCopyableViewInWebContents(web_contents());
  observer.Wait();

  AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
  ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
}

// Capture for renderer initiated history back navigation via `history.back()`.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest, HistoryDotBack) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(
        NavigateToURLFromRenderer(web_contents(), GetNextUrl("/green.html")));
    WaitForCopyableViewInWebContents(web_contents());
    observer.Wait();

    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green*] -> [red*, green&]");
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    auto* rfh = web_contents()->GetPrimaryMainFrame();
    TestFrameNavigationObserver nav_observer(rfh);
    ASSERT_TRUE(ExecJs(rfh, "window.history.back();"));
    nav_observer.Wait();
    observer.Wait();

    AssertOrderedScreenshotsAre(controller, {std::nullopt, SK_ColorGREEN});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
}

// Asserting that both the navigations from and to the about:blank triggers
// screenshot capture.
// Disabled because flaky. (See b/354018428)
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       DISABLED_AboutBlankCaptured) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);

  // Creates a new tab and loads about:blank.
  auto* shell = Shell::CreateNewWindow(
      ShellContentBrowserClient::Get()->browser_context(),
      GURL(url::kAboutBlankURL),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab = static_cast<WebContentsImpl*>(shell->web_contents());
  const std::string kRemoveScrollbar = R"(
    const meta = document.createElement("meta");
    meta.name = "viewport";
    meta.content = "width=device-width,minimum-scale=1";
    document.head.appendChild(meta);
  )";
  ASSERT_TRUE(ExecJs(tab, kRemoveScrollbar));
  WaitForCopyableViewInWebContents(tab);

  auto& controller = tab->GetController();
  ASSERT_EQ(controller.GetEntryCount(), 1);
  ASSERT_FALSE(controller.GetLastCommittedEntry()->IsInitialEntry());
  ASSERT_EQ(controller.GetLastCommittedEntry()->GetURL(),
            GURL(url::kAboutBlankURL));

  // Navigates away from about:blank.
  ASSERT_TRUE(NavigateToURL(tab, GetNextUrl("/green.html")));
  WaitForCopyableViewInWebContents(tab);
  // Captured.
  AssertOrderedScreenshotsAre(controller, {SK_ColorWHITE, std::nullopt});

  HistoryNavigateTabAndWaitForScreenshotCached(tab, controller, -1);
  // Captured.
  AssertOrderedScreenshotsAre(controller, {std::nullopt, SK_ColorGREEN});
}

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest, Redirect) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  const auto next_host = GetNextHost();
  const auto redirect_gurl = embedded_test_server()->GetURL(
      "/cross-site/" + next_host + "/green.html");
  const auto expected_gurl =
      embedded_test_server()->GetURL(next_host, "/green.html");
  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), redirect_gurl, expected_gurl));
    WaitForCopyableViewInWebContents(web_contents());
    observer.Wait();
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green*] -> [red*, green&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorRED);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(controller, {std::nullopt, SK_ColorGREEN});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
}

// We don't capture if we simply reload the page.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       Reload_NotCaptured) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  SCOPED_TRACE("Reload.");
  controller.Reload(content::ReloadType::NORMAL, false);
  ASSERT_TRUE(WaitForLoadStop(web_contents()));
  // No requests issued.
  ASSERT_EQ(
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(), 0);

  AssertOrderedScreenshotsAre(controller, {std::nullopt});
  ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
}

// Testing that the navigation via `window.location.replace` won't trigger a
// capture.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       LocationDotReplace_NotCaptured) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  SCOPED_TRACE("`window.location.replace`");
  const GURL green_url =
      embedded_test_server()->GetURL(GetNextHost(), "/green.html");
  auto* rfh = web_contents()->GetPrimaryMainFrame();
  TestFrameNavigationObserver nav_observer(rfh);
  ASSERT_TRUE(
      ExecJs(rfh, JsReplace("window.location.replace($1);", green_url)));
  // No requests issued.
  ASSERT_EQ(
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(), 0);

  AssertOrderedScreenshotsAre(controller, {std::nullopt});
  ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
}

// Testing that the navigation with a 204 response won't trigger a capture.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       NavigationTo204_NotCaptured) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  const GURL url_204 =
      embedded_test_server()->GetURL(GetNextHost(), "/page204.html");
  ASSERT_TRUE(NavigateToURLAndExpectNoCommit(shell(), url_204));
  // No requests issued.
  ASSERT_EQ(
      NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(), 0);

  AssertOrderedScreenshotsAre(controller, {std::nullopt});
  ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
}

namespace {
// This assumes the top 50% is the embedder and the bottom 50% is an iframe.
void AssertScreenshotForPageWithIFrameIs(NavigationEntry* entry,
                                         SkColor embedder,
                                         SkColor iframe) {
  auto* screenshot = PreviewScreenshotForEntry(entry);
  ASSERT_NE(screenshot, nullptr);
  const auto size = screenshot->dimensions_without_compression();
  auto bitmap = screenshot->GetBitmapForTesting();

  int half_height = size.height() / 2;
  bool is_height_odd = (size.height() % 2);

  // Expect the embedder's color matches.
  NavigationEntryScreenshotBrowserTest::ExpectBitmapRowsAreColor(
      bitmap, embedder, gfx::Rect(0, 0, bitmap.width(), half_height));

  // Expect the iframe's color matches. Skip checking the middle row if the
  // height is an odd number.
  int iframe_height_start = is_height_odd ? half_height + 1 : half_height;
  NavigationEntryScreenshotBrowserTest::ExpectBitmapRowsAreColor(
      bitmap, iframe,
      gfx::Rect(0, iframe_height_start, bitmap.width(), half_height));
}
}  // namespace

// Asserts that no screenshots captured for the navigations of iframes.
//
// TODO(crbug.com/40896219): Support iframe navigations.
//
// TODO(crbug.com/340929354): Reenable the test.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
                       DISABLED_SameOriginIFrame_NotCaptured) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  const std::string kCreateIFrameWithID = R"(
    const style = document.createElement("style");
    style.textContent = "iframe { width: 100vw; height: 50vh; position: fixed; top: 50vh; left: 0; border: 0; }";
    document.head.appendChild(style);

    const iframe = document.createElement("iframe");
    iframe.id = $1;
    document.body.appendChild(iframe);
  )";

  {
    SCOPED_TRACE("[red*] -> [red(empty)*]");
    ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
                       JsReplace(kCreateIFrameWithID, "iframe_id")));
    NavigateIframeToURL(web_contents(), "iframe_id", GetNextUrl("/green.html"));
    WaitForCopyableViewInWebContents(web_contents());
    // Insert an <iframe> into DOM does not create a navigation entry. This
    // navigation won't trigger a capture because it is not from the primary
    // main frame.
    AssertOrderedScreenshotsAre(controller, {std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
  }
  {
    SCOPED_TRACE("[red(green)*] -> [red(green), red(title1)*]");
    NavigateIframeToURL(web_contents(), "iframe_id",
                        GetNextUrl("/title1.html"));
    WaitForCopyableViewInWebContents(web_contents());
    AssertOrderedScreenshotsAre(controller, {std::nullopt, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
  }
  {
    // History navigation on the subframe. No capture.
    SCOPED_TRACE("[red(green), red(title1)*] -> [red(green)*, red(title1)]");
    ASSERT_TRUE(HistoryGoBack(web_contents()));
    WaitForCopyableViewInWebContents(web_contents());
    AssertOrderedScreenshotsAre(controller, {std::nullopt, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 0U);
  }
  {
    // Main frame navigation. Capture.
    SCOPED_TRACE("[red(green)*, red(title1)] -> [red(green)&, title2*]");
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    ASSERT_TRUE(NavigateToURL(web_contents(), GetNextUrl("/title2.html")));
    WaitForCopyableViewInWebContents(web_contents());
    observer.Wait();
    ASSERT_EQ(controller.GetEntryCount(), 2);
    AssertScreenshotForPageWithIFrameIs(controller.GetEntryAtIndex(0),
                                        SK_ColorRED, SK_ColorGREEN);
    ASSERT_EQ(PreviewScreenshotForEntry(controller.GetEntryAtIndex(1)),
              nullptr);
    ASSERT_EQ(manager->GetCurrentCacheSize(), page_size);
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         NavigationEntryScreenshotBrowserTest,
                         ::testing::ValuesIn(kNavTypes),
                         &DescribeNavType);

class NavigationEntryScreenshotBrowserTestWithEviction
    : public NavigationEntryScreenshotBrowserTest {
 public:
  void SetUp() override {
    if (base::android::BuildInfo::GetInstance()->is_automotive()) {
      GTEST_SKIP() << "This test is flaky on automotive. crbug.com/358342700";
    }
    NavigationEntryScreenshotBrowserTest::SetUp();
  }
  bool Use1MinuteEvictionDelay() const override { return true; }
  ~NavigationEntryScreenshotBrowserTestWithEviction() override = default;
};

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestWithEviction,
                       InvisibleTabEviction) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
  manager->SetUITaskRunnerForTesting(task_runner);

  {
    SCOPED_TRACE("tab1: [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }

  // Creates a second tab within the same profile such that two tabs share the
  // same manager. `NavigationEntryScreenshotManager` is per Profile
  // (`BrowserContext`) - it budgets the memories for all the screenshots across
  // different tabs.
  auto* shell2 = Shell::CreateNewWindow(
      shell()->web_contents()->GetBrowserContext(), GetNextUrl("/red.html"),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab2 = static_cast<WebContentsImpl*>(shell2->web_contents());
  EXPECT_EQ(manager, GetManagerForTab(tab2));
  EXPECT_TRUE(tab2->GetController().GetLastCommittedEntry()->IsInitialEntry());
  WaitForCopyableViewInWebContents(tab2);
  // We don't capture for the initial entry.
  EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  auto& controller2 = tab2->GetController();
  EXPECT_EQ(controller2.GetEntryCount(), 1);

  {
    SCOPED_TRACE(
        "tab1: [red&, green&, blue*] -> [red&, green&, blue*] (no change); "
        "tab2: [red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(controller2, {SK_ColorRED, std::nullopt});
    // No change in tab1, because we have one cache slot for a new screenshot in
    // tab2.
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }

  base::SimpleTestTickClock fake_clock;
  manager->set_tick_clock_for_testing(&fake_clock);
  const base::TimeDelta eviction_delay = base::Minutes(1);
  fake_clock.SetNowTicks(base::TimeTicks() + eviction_delay);

  // Mark the tabs hidden at different times.
  web_contents()->WasHidden();
  fake_clock.Advance(eviction_delay);
  tab2->WasHidden();

  // A task should be posted to clear the first tab.
  ASSERT_TRUE(task_runner->HasPendingTask());
  task_runner->RunPendingTasks();
  EXPECT_TRUE(controller.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_FALSE(controller2.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), page_size);
  EXPECT_EQ(
      controller.GetEntryAtIndex(0)
          ->navigation_transition_data()
          .cache_hit_or_miss_reason(),
      NavigationTransitionData::CacheHitOrMissReason::kCacheMissInvisible);
  EXPECT_EQ(
      controller.GetEntryAtIndex(1)
          ->navigation_transition_data()
          .cache_hit_or_miss_reason(),
      NavigationTransitionData::CacheHitOrMissReason::kCacheMissInvisible);

  // Another task to clear the second tab.
  ASSERT_TRUE(task_runner->HasPendingTask());
  fake_clock.Advance(eviction_delay);
  task_runner->RunPendingTasks();
  EXPECT_TRUE(controller2.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), 0u);
  EXPECT_EQ(
      controller2.GetEntryAtIndex(0)
          ->navigation_transition_data()
          .cache_hit_or_miss_reason(),
      NavigationTransitionData::CacheHitOrMissReason::kCacheMissInvisible);

  // No more pending tasks since all invisible tabs have been purged.
  EXPECT_FALSE(task_runner->HasPendingTask());

  manager->set_tick_clock_for_testing(nullptr);
}

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestWithEviction,
                       VisibleHiddenVisibleNotEvicted) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
  manager->SetUITaskRunnerForTesting(task_runner);

  {
    SCOPED_TRACE("tab1: [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }

  base::SimpleTestTickClock fake_clock;
  manager->set_tick_clock_for_testing(&fake_clock);
  const base::TimeDelta eviction_delay = base::Minutes(1);
  fake_clock.SetNowTicks(base::TimeTicks() + eviction_delay);

  // A task should be posted to clear the tab.
  web_contents()->WasHidden();
  ASSERT_TRUE(task_runner->HasPendingTask());

  // Make the tab visible and advance the time to trigger the queued up task to
  // clear.
  web_contents()->WasShown();
  fake_clock.Advance(eviction_delay);
  task_runner->RunPendingTasks();
  EXPECT_FALSE(controller.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);

  // No more pending tasks since there's no invisible tabs.
  EXPECT_FALSE(task_runner->HasPendingTask());

  manager->set_tick_clock_for_testing(nullptr);
}

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestWithEviction,
                       MultipleInvisibleTabs) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);

  auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
  manager->SetUITaskRunnerForTesting(task_runner);

  auto& controller = web_contents()->GetController();
  {
    SCOPED_TRACE("tab1: [red&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    EXPECT_EQ(manager->GetCurrentCacheSize(), page_size);
  }

  // Creates more tabs within the same profile such that two tabs share the
  // same manager. `NavigationEntryScreenshotManager` is per Profile
  // (`BrowserContext`) - it budgets the memories for all the screenshots across
  // different tabs.
  auto* shell2 = Shell::CreateNewWindow(
      shell()->web_contents()->GetBrowserContext(), GetNextUrl("/red.html"),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab2 = static_cast<WebContentsImpl*>(shell2->web_contents());
  WaitForCopyableViewInWebContents(tab2);
  auto& controller2 = tab2->GetController();
  {
    SCOPED_TRACE("tab2: [red&, blue*]");
    NavigateTabAndWaitForScreenshotCached(tab2, controller2,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller2, {SK_ColorRED, std::nullopt});
    EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }

  auto* shell3 = Shell::CreateNewWindow(
      shell()->web_contents()->GetBrowserContext(), GetNextUrl("/red.html"),
      /*site_instance=*/nullptr, gfx::Size());
  auto* tab3 = static_cast<WebContentsImpl*>(shell3->web_contents());
  WaitForCopyableViewInWebContents(tab3);
  auto& controller3 = tab3->GetController();
  {
    SCOPED_TRACE("tab3: [red&, blue*]");
    NavigateTabAndWaitForScreenshotCached(tab3, controller3,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller3, {SK_ColorRED, std::nullopt});
    EXPECT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }

  base::SimpleTestTickClock fake_clock;
  manager->set_tick_clock_for_testing(&fake_clock);
  const base::TimeDelta eviction_delay = base::Minutes(1);
  fake_clock.SetNowTicks(base::TimeTicks() + eviction_delay);

  // Mark the tabs hidden at different times.
  const auto delay = base::Seconds(10);
  web_contents()->WasHidden();
  fake_clock.Advance(delay);
  tab2->WasHidden();
  fake_clock.Advance(delay);
  tab3->WasHidden();

  // A task should be posted to clear the first tab.
  ASSERT_TRUE(task_runner->HasPendingTask());
  fake_clock.Advance(eviction_delay - 2 * delay);
  task_runner->RunPendingTasks();
  EXPECT_TRUE(controller.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_FALSE(controller2.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_FALSE(controller3.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);

  // Another task to clear the second tab.
  ASSERT_TRUE(task_runner->HasPendingTask());
  fake_clock.Advance(delay);
  task_runner->RunPendingTasks();
  EXPECT_TRUE(controller.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_TRUE(controller2.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_FALSE(controller3.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), page_size);

  // Another task to clear the third tab.
  ASSERT_TRUE(task_runner->HasPendingTask());
  fake_clock.Advance(delay);
  task_runner->RunPendingTasks();
  EXPECT_TRUE(controller.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_TRUE(controller2.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_TRUE(controller3.GetNavigationEntryScreenshotCache()->IsEmpty());
  EXPECT_EQ(manager->GetCurrentCacheSize(), 0u);

  // No more pending tasks since all invisible tabs have been purged.
  EXPECT_FALSE(task_runner->HasPendingTask());

  manager->set_tick_clock_for_testing(nullptr);
}

INSTANTIATE_TEST_SUITE_P(All,
                         NavigationEntryScreenshotBrowserTestWithEviction,
                         ::testing::ValuesIn(kNavTypes),
                         &DescribeNavType);

class NavigationEntryScreenshotBrowserTestPrefersReducedMotion
    : public NavigationEntryScreenshotBrowserTest {
 public:
  ~NavigationEntryScreenshotBrowserTestPrefersReducedMotion() override =
      default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    NavigationEntryScreenshotBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kForcePrefersReducedMotion);
  }
};

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestPrefersReducedMotion,
                       NoCapture) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("tab1: [red, green*]");
    ASSERT_TRUE(NavigateToURL(web_contents(), GetNextUrl("/green.html")));
    auto* entry = controller.GetEntryAtIndex(0);
    EXPECT_EQ(entry->navigation_transition_data().cache_hit_or_miss_reason(),
              NavigationTransitionData::CacheHitOrMissReason::
                  kCacheMissPrefersReducedMotion);
    EXPECT_EQ(
        NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(),
        0);
    EXPECT_EQ(manager->GetCurrentCacheSize(), 0u);
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    NavigationEntryScreenshotBrowserTestPrefersReducedMotion,
    ::testing::ValuesIn(kNavTypes),
    &DescribeNavType);

class NavigationEntryScreenshotBrowserTestWithWebUI
    : public NavigationEntryScreenshotBrowserTest {
 public:
  ~NavigationEntryScreenshotBrowserTestWithWebUI() override = default;

 private:
  // Used to make the "web-ui" prefixed documents being rendered as WebUIs.
  TestWebUIControllerFactory factory_;
  ScopedWebUIControllerFactoryRegistration factory_registration_{&factory_};
};

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestWithWebUI,
                       PrimaryMainFrameNavWebUI) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  GURL webui_red(GetWebUIURL("web-ui/red.html"));
  GURL webui_green(GetWebUIURL("web-ui/green.html"));

  {
    SCOPED_TRACE("[red*] -> [red&, webui_green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          webui_green);
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, webui_green*] -> [red&, webui_green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }
  {
    SCOPED_TRACE(
        "[red&, webui_green&, blue*] -> "
        "[red&, webui_green&, blue&, webui_green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          webui_green);
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red&, webui_green&, blue&, webui_green*] -> "
        "[red, webui_green&, blue&, webui_green&, webui_red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          webui_red);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorGREEN,
                     std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, webui_green&, blue&, webui_green&, webui_red*] -> "
        "[red, webui_green, blue&, webui_green&, webui_red&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/red.html"));
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, SK_ColorGREEN,
                     SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, webui_green, blue&, webui_green&, webui_red&, red*] -> "
        "[red, webui_green, blue&, webui_green&, webui_red*, red&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorRED);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, SK_ColorBLUE, SK_ColorGREEN,
                     std::nullopt, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, webui_green, blue&, webui_green&, webui_red*, red&] -> "
        "[red, webui_green, blue*, webui_green&, webui_red&, red&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-2));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorBLUE);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -2);
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, std::nullopt, SK_ColorGREEN,
                     SK_ColorRED, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         NavigationEntryScreenshotBrowserTestWithWebUI,
                         ::testing::ValuesIn(kNavTypes),
                         &DescribeNavType);

class NavigationEntryScreenshotBrowserTestWithPrerender
    : public NavigationEntryScreenshotBrowserTest {
 public:
  NavigationEntryScreenshotBrowserTestWithPrerender() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // `prerender_helper_` has its own `ScopedFeatureLists`. We need to init
    // the test base's `ScopedFeatureLists` to respect the destruction order.
    NavigationEntryScreenshotBrowserTest::SetUpCommandLine(command_line);

    prerender_helper_ = std::make_unique<test::PrerenderTestHelper>(
        base::BindLambdaForTesting([&]() { return shell()->web_contents(); }));
  }

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

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

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTestWithPrerender,
                       PrerenderActivation) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), page_size);
  }

  // Add a prerender and navigate the main frame to the prerendered URL. The
  // the prerender's document-fetching navigation request is not in the primary
  // main frame so no screenshot is captured.
  const auto prerender_gurl = GetNextUrl("/title1.html");
  prerender_helper()->AddPrerender(prerender_gurl);
  // No change in the screenshots.
  AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
  ASSERT_EQ(manager->GetCurrentCacheSize(), page_size);

  // Activate the prerendered URL by navigating the primary main frame.
  {
    SCOPED_TRACE("[red&, green*] -> [red&, green&, title1*]");
    test::PrerenderHostObserver activation_obs(*web_contents(), prerender_gurl);
    ScopedScreenshotCapturedObserverForTesting observer(
        web_contents()->GetController().GetLastCommittedEntryIndex());
    prerender_helper()->NavigatePrimaryPage(prerender_gurl);
    observer.Wait();
    activation_obs.WaitForActivation();
    ASSERT_TRUE(activation_obs.was_activated());
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    NavigationEntryScreenshotBrowserTestWithPrerender,
    // Prerender requires same-origin URL (a.com).
    ::testing::ValuesIn(
        {ScreenshotCaptureTestNavigationType{.same_origin = true,
                                             .enable_bfcache = true},
         ScreenshotCaptureTestNavigationType{.same_origin = true,
                                             .enable_bfcache = false}}),
    &DescribeNavType);

namespace {

void NavigateTabAndWaitForScreenshotCachedSameDoc(
    WebContents* tab,
    NavigationControllerImpl& controller,
    const GURL& destination) {
  NavigateTabAndWaitForScreenshotCached(tab, controller, destination, true);
}

void HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(
    WebContents* tab,
    NavigationControllerImpl& controller,
    int offset) {
  HistoryNavigateTabAndWaitForScreenshotCached(tab, controller, offset, true);
}

}  // namespace

class SameDocNavigationEntryScreenshotBrowserTest
    : public NavigationEntryScreenshotBrowserTestBase {
 public:
  SameDocNavigationEntryScreenshotBrowserTest() = default;
  ~SameDocNavigationEntryScreenshotBrowserTest() override = default;

  void SetUp() override {
    if (base::SysInfo::GetAndroidHardwareEGL() == "emulation") {
      // crbug.com/337886037 and crrev.com/c/5504854/comment/b81b8fb6_95fb1381/:
      // The CopyOutputRequests crash the GPU process. ANGLE is exporting the
      // native fence support on Android emulators but it doesn't work properly.
      GTEST_SKIP();
    }
    NavigationEntryScreenshotBrowserTestBase::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {viz::mojom::EnableVizTestApis, {}},
        {blink::features::kBackForwardTransitions, {}},
        {blink::features::kIncrementLocalSurfaceIdForMainframeSameDocNavigation,
         {}}};

    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});

    // Disable the vertical scroll bar, otherwise they might show up on the
    // screenshot, making the test flaky.
    command_line->AppendSwitch(switches::kHideScrollbars);
    NavigationEntryScreenshotBrowserTestBase::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    NavigationEntryScreenshotBrowserTestBase::SetUpOnMainThread();

    ASSERT_TRUE(NavigateToURL(web_contents(), embedded_test_server()->GetURL(
                                                  "/changing_color.html")));
    WaitForCopyableViewInWebContents(web_contents());

    mojo::ScopedAllowSyncCallForTesting allowed_for_testing;
    GetHostFrameSinkManager()
        ->GetFrameSinkManagerTestApi()
        .SetSameDocNavigationScreenshotSize(GetScaledViewportSize());
  }

  gfx::Rect GetCompareRegion() { return gfx::Rect(GetScaledViewportSize()); }

  GURL GetURL(const std::string& hash) {
    return embedded_test_server()->GetURL("/changing_color.html" + hash);
  }

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

IN_PROC_BROWSER_TEST_F(SameDocNavigationEntryScreenshotBrowserTest, Basic) {
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 10 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
                                                 GetURL("#green"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt},
                                GetCompareRegion());
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
                                                 GetURL("#blue"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit,
                     CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt},
                                GetCompareRegion());
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green&, blue*] -> [red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
                                                 GetURL("#red"));
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt},
        GetCompareRegion());
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green&, blue&, red*] -> [red&, green&, blue*, red&]");
    HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(),
                                                        controller, -1);
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         std::nullopt, CacheHitOrMissReason::kCacheHit});
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, std::nullopt, SK_ColorRED},
        GetCompareRegion());
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green&, blue*, red&] -> [red*, green&, blue&, red&]");
    HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(),
                                                        controller, -2);
    AssertCacheHitOrMissReasonsAre(
        controller,
        {std::nullopt, CacheHitOrMissReason::kCacheHit,
         CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit});
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED},
        GetCompareRegion());
    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
  }
}

class SameDocNavigationEntryScreenshotBrowserTestPrefersReducedMotion
    : public SameDocNavigationEntryScreenshotBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    SameDocNavigationEntryScreenshotBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kForcePrefersReducedMotion);
  }
};

IN_PROC_BROWSER_TEST_F(
    SameDocNavigationEntryScreenshotBrowserTestPrefersReducedMotion,
    NoCapture) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("tab1: [red, green*]");
    NavigationTransitionUtils::ResetNumCopyOutputRequestIssuedForTesting();
    ASSERT_TRUE(NavigateToURL(web_contents(), GetURL("#green")));
    auto* entry = controller.GetEntryAtIndex(0);
    EXPECT_EQ(
        NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting(),
        0);
    EXPECT_FALSE(entry->navigation_transition_data()
                     .same_document_navigation_entry_screenshot_token()
                     .has_value());
    EXPECT_EQ(entry->navigation_transition_data().cache_hit_or_miss_reason(),
              NavigationTransitionData::CacheHitOrMissReason::
                  kCacheMissPrefersReducedMotion);
    EXPECT_EQ(manager->GetCurrentCacheSize(), 0u);
  }
}

namespace {
using NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest =
    NavigationEntryScreenshotBrowserTest;
}  // namespace

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest,
                       BasicNavigations) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
  }
  {
    SCOPED_TRACE("[red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit,
                     CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
  }
  {
    SCOPED_TRACE(
        "[red&, green&, blue*] -> "
        "[red&, green&, blue&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(
        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red&, green&, blue&, green*] -> "
        "[red, green&, blue&, green&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/red.html"));
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheMissEvicted,
         CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorGREEN,
                     std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green&, blue&, green&, red*] -> "
        "[red, green&, blue&, green*, red&]");
    {
      std::unique_ptr<NavigationEntryScreenshot> screenshot =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot.get(), SK_ColorGREEN);
      AssertCacheHitOrMissReasonsAre(
          controller,
          {CacheHitOrMissReason::kCacheMissEvicted,
           CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
           std::nullopt, std::nullopt});
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheMissEvicted,
         CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         std::nullopt, CacheHitOrMissReason::kCacheHit});
    AssertOrderedScreenshotsAre(
        controller,
        {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, std::nullopt, SK_ColorRED});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
}

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest,
                       PurgeForMemoryPressure) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red, green&, blue&, green&, green_CCNS&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/blue.html"));
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    NavigateTabAndWaitForScreenshotCached(
        web_contents(), controller,
        GetNextUrl("/set-header?Cache-Control: no-store"));
    ASSERT_TRUE(NavigateToURL(web_contents(), GetNextUrl("/red.html")));
    AssertCacheHitOrMissReasonsAre(
        controller,
        {CacheHitOrMissReason::kCacheMissEvicted,
         CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheHit,
         CacheHitOrMissReason::kCacheHit, CacheHitOrMissReason::kCacheMissCCNS,
         std::nullopt});
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorGREEN,
                     std::nullopt, std::nullopt});
    ASSERT_EQ(manager->GetCurrentCacheSize(), memory_budget);
  }
  {
    SCOPED_TRACE(
        "[red, green&, blue&, green&, green_CCNS&, red*] -> "
        "[red, green, blue, green, green_CCNS, red]");

    controller.GetNavigationEntryScreenshotCache()->Purge(
        NavigationEntryScreenshotCacheEvictor::PurgeReason::kMemoryPressure);
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheMissEvicted,
                     CacheHitOrMissReason::kCacheMissPurgedMemoryPressure,
                     CacheHitOrMissReason::kCacheMissPurgedMemoryPressure,
                     CacheHitOrMissReason::kCacheMissPurgedMemoryPressure,
                     CacheHitOrMissReason::kCacheMissCCNS, std::nullopt});
    AssertOrderedScreenshotsAre(
        controller, {std::nullopt, std::nullopt, std::nullopt, std::nullopt,
                     std::nullopt, std::nullopt});
  }
}

// To make sure the enum is set correctly when the user initiates a preview, the
// cancels the gesture, then re-initiates the gesture.
IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest,
                       CancelAndReinitiateGesture) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, green&]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                          GetNextUrl("/green.html"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
  }
  {
    SCOPED_TRACE(
        "[red&, green*] -> "
        "[red*, green&]");
    {
      // Testing the scenario of canceling a gesture and redoing it.
      // (e.g. Cancelling a swipe and swiping again after.)
      std::unique_ptr<NavigationEntryScreenshot> screenshot1 =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      AssertCacheHitOrMissReasonsAre(controller, {std::nullopt, std::nullopt});

      controller.GetNavigationEntryScreenshotCache()->SetScreenshot(
          nullptr, std::move(screenshot1), false);
      AssertCacheHitOrMissReasonsAre(
          controller, {CacheHitOrMissReason::kCacheHit, std::nullopt});

      std::unique_ptr<NavigationEntryScreenshot> screenshot2 =
          controller.GetNavigationEntryScreenshotCache()->RemoveScreenshot(
              controller.GetEntryAtOffset(-1));
      ExpectScreenshotIsColor(screenshot2.get(), SK_ColorRED);
    }
    HistoryNavigateTabAndWaitForScreenshotCached(web_contents(), controller,
                                                 -1);
    AssertCacheHitOrMissReasonsAre(
        controller, {std::nullopt, CacheHitOrMissReason::kCacheHit});
    AssertOrderedScreenshotsAre(controller, {std::nullopt, SK_ColorGREEN});
  }
}

IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest,
                       CCNSMissReason) {
  // Max of three screenshots per Profile (BrowserContext).
  const size_t page_size = GetUncompressedScreenshotSizeInBytes();
  const size_t memory_budget = 3 * page_size;
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(memory_budget);
  auto& controller = web_contents()->GetController();

  {
    SCOPED_TRACE("[red*] -> [red&, red_CCNS*]");
    NavigateTabAndWaitForScreenshotCached(
        web_contents(), controller,
        GetNextUrl("/set-header?Cache-Control: no-store"));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit, std::nullopt});
    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt});
  }
  {
    SCOPED_TRACE("[red&, red_CCNS*] -> [red&, red_CCNS&, green*]");
    ASSERT_TRUE(NavigateToURL(web_contents(), GetNextUrl("/green.html")));
    AssertCacheHitOrMissReasonsAre(
        controller, {CacheHitOrMissReason::kCacheHit,
                     CacheHitOrMissReason::kCacheMissCCNS, std::nullopt});
    AssertOrderedScreenshotsAre(controller,
                                {SK_ColorRED, std::nullopt, std::nullopt});
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    NavigationEntryScreenshotCacheHitOrMissReasonBrowserTest,
    ::testing::ValuesIn(kNavTypes),
    &DescribeNavType);

class NavigationEntryScreenshotCompressionBrowserTest
    : public NavigationEntryScreenshotBrowserTestBase {
 public:
  NavigationEntryScreenshotCompressionBrowserTest() = default;
  ~NavigationEntryScreenshotCompressionBrowserTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    scoped_feature_list_.InitAndEnableFeature(
        blink::features::kBackForwardTransitions);
    NavigationEntryScreenshotBrowserTestBase::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    NavigationEntryScreenshotBrowserTestBase::SetUpOnMainThread();

    ASSERT_TRUE(NavigateToURL(web_contents(),
                              embedded_test_server()->GetURL("/red.html")));
    WaitForCopyableViewInWebContents(web_contents());
  }

  bool EnableCompression() const override { return true; }

  GURL GetNextUrl(std::string_view path) const {
    return embedded_test_server()->GetURL(path);
  }

  NavigationControllerImpl& controller() {
    return web_contents()->GetController();
  }

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

IN_PROC_BROWSER_TEST_F(NavigationEntryScreenshotCompressionBrowserTest, Basic) {
  // Start with only 1 regular screenshot allowed.
  const size_t screenshot_bytes = GetUncompressedScreenshotSizeInBytes();
  auto* manager = GetManagerForTab(web_contents());
  manager->SetMemoryBudgetForTesting(screenshot_bytes);
  size_t compressed_screenshot_bytes = 0u;

  {
    SCOPED_TRACE("[red*] -> [red&, green*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller(),
                                          GetNextUrl("/green.html"));
    AssertOrderedScreenshotsAre(controller(), {SK_ColorRED, std::nullopt});

    compressed_screenshot_bytes =
        NavigationTransitionTestUtils::WaitForScreenshotCompressed(
            controller(), controller().GetLastCommittedEntryIndex() - 1);
    EXPECT_GT(compressed_screenshot_bytes, 0u);
    EXPECT_LT(compressed_screenshot_bytes, screenshot_bytes);
    ASSERT_EQ(manager->GetCurrentCacheSize(), compressed_screenshot_bytes);
  }

  // 1 regular and 1 compressed screenshot allowed.
  manager->SetMemoryBudgetForTesting(screenshot_bytes +
                                     compressed_screenshot_bytes);

  {
    SCOPED_TRACE("[red&, green*] -> [red&, green&, blue*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller(),
                                          GetNextUrl("/blue.html"));
    AssertOrderedScreenshotsAre(controller(),
                                {SK_ColorRED, SK_ColorGREEN, std::nullopt});
    EXPECT_EQ(compressed_screenshot_bytes,
              NavigationTransitionTestUtils::WaitForScreenshotCompressed(
                  controller(), controller().GetLastCommittedEntryIndex() - 1));
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * compressed_screenshot_bytes);
  }

  {
    SCOPED_TRACE("[red&, green&, blue*] -> [red&, green&, blue&, red*]");
    NavigateTabAndWaitForScreenshotCached(web_contents(), controller(),
                                          GetNextUrl("/red.html"));
    AssertOrderedScreenshotsAre(controller(), {std::nullopt, SK_ColorGREEN,
                                               SK_ColorBLUE, std::nullopt});
    EXPECT_EQ(compressed_screenshot_bytes,
              NavigationTransitionTestUtils::WaitForScreenshotCompressed(
                  controller(), controller().GetLastCommittedEntryIndex() - 1));
    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * compressed_screenshot_bytes);
  }
}

}  // namespace content