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

#include <stdint.h>

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/notimplemented.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/slim/layer_tree.h"
#include "cc/slim/surface_layer.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/renderer_host/browser_compositor_ios.h"
#include "content/browser/renderer_host/dip_util.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/test_render_widget_host_view_ios_factory.h"
#include "content/browser/renderer_host/visible_time_request_trigger.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.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/slow_http_response.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/render_document_feature.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/page/content_to_visible_time_reporter.h"
#include "third_party/blink/public/common/page/content_to_visible_time_request.h"
#include "third_party/blink/public/mojom/page/page_visibility_state.mojom-shared.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/compositor/layer_solid_color.h"
#include "ui/display/display_switches.h"
#include "ui/gfx/geometry/size_conversions.h"

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif

#if defined(USE_AURA)
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#endif

#if BUILDFLAG(IS_ANDROID)
#include "content/browser/renderer_host/compositor_impl_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "ui/android/delegated_frame_host_android.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "content/browser/renderer_host/browser_compositor_view_mac.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/test_render_widget_host_view_mac_factory.h"
#include "content/public/browser/context_factory.h"
#include "third_party/blink/public/common/page/content_to_visible_time_reporter.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer_test_api.h"
#include "ui/compositor/recyclable_compositor_mac.h"
#endif

namespace content {

namespace {

// Convenience macro: Short-circuit a pass for the tests where platform support
// for forced-compositing mode (or disabled-compositing mode) is lacking.
#define SET_UP_SURFACE_OR_PASS_TEST(wait_message)  \
  if (!SetUpSourceSurface(wait_message)) {  \
    LOG(WARNING)  \
        << ("Blindly passing this test: This platform does not support "  \
            "forced compositing (or forced-disabled compositing) mode.");  \
    return;  \
  }

}  // namespace

// Common base class for browser tests.  This is subclassed three times: Once to
// test the browser in forced-compositing mode; once to test with compositing
// mode disabled; once with no surface creation for non-visual tests.
class RenderWidgetHostViewBrowserTest : public ContentBrowserTest {
 public:
  RenderWidgetHostViewBrowserTest()
      : frame_size_(400, 300),
        callback_invoke_count_(0),
        frames_captured_(0) {}

  void SetUpOnMainThread() override {
    ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_dir_));
  }

  // Attempts to set up the source surface.  Returns false if unsupported on the
  // current platform.
  virtual bool SetUpSourceSurface(const char* wait_message) = 0;

  int callback_invoke_count() const {
    return callback_invoke_count_;
  }

  int frames_captured() const {
    return frames_captured_;
  }

  const gfx::Size& frame_size() const {
    return frame_size_;
  }

  const base::FilePath& test_dir() const {
    return test_dir_;
  }

  RenderViewHost* GetRenderViewHost() const {
    RenderViewHost* const rvh =
        shell()->web_contents()->GetPrimaryMainFrame()->GetRenderViewHost();
    CHECK(rvh);
    return rvh;
  }

  RenderWidgetHostImpl* GetRenderWidgetHost() const {
    RenderWidgetHostImpl* const rwh = RenderWidgetHostImpl::From(
        shell()->web_contents()->GetRenderWidgetHostView()->
            GetRenderWidgetHost());
    CHECK(rwh);
    return rwh;
  }

  RenderWidgetHostViewBase* GetRenderWidgetHostView() const {
    return static_cast<RenderWidgetHostViewBase*>(
        GetRenderViewHost()->GetWidget()->GetView());
  }

  // Callback when using CopyFromSurface() API.
  void FinishCopyFromSurface(base::OnceClosure quit_closure,
                             const content::CopyFromSurfaceResult& result) {
    ++callback_invoke_count_;
    if (result.has_value()) {
      ++frames_captured_;
    }
    std::move(quit_closure).Run();
  }

 protected:
  // Waits until the source is available for copying.
  void WaitForCopySourceReady() {
    while (!GetRenderWidgetHostView()->IsSurfaceAvailableForCopy())
      GiveItSomeTime();
  }

  // Run the current message loop for a short time without unwinding the current
  // call stack.
  static void GiveItSomeTime() {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
    run_loop.Run();
  }

 private:
  const gfx::Size frame_size_;
  base::FilePath test_dir_;
  int callback_invoke_count_;
  int frames_captured_;
};

// Helps to ensure that a navigation is committed after a compositor frame was
// submitted by the renderer, but before corresponding ACK is sent back.
class CommitBeforeSwapAckSentHelper : public DidCommitNavigationInterceptor {
 public:
  explicit CommitBeforeSwapAckSentHelper(
      WebContents* web_contents,
      RenderFrameSubmissionObserver* frame_observer)
      : DidCommitNavigationInterceptor(web_contents),
        frame_observer_(frame_observer) {}

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

 private:
  // DidCommitNavigationInterceptor:
  bool WillProcessDidCommitNavigation(
      RenderFrameHost* render_frame_host,
      NavigationRequest* navigation_request,
      mojom::DidCommitProvisionalLoadParamsPtr* params,
      mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
      override {
    frame_observer_->WaitForAnyFrameSubmission();
    return true;
  }

  // Not owned.
  const raw_ptr<RenderFrameSubmissionObserver> frame_observer_;
};

class RenderWidgetHostViewBrowserTestBase : public ContentBrowserTest {
 public:
  ~RenderWidgetHostViewBrowserTestBase() override {}

 protected:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
  }
};

// Base class for testing a RenderWidgetHostViewBase where visual output is not
// relevant. This class does not setup surfaces for compositing.
class NoCompositingRenderWidgetHostViewBrowserTest
    : public RenderWidgetHostViewBrowserTest {
 public:
  NoCompositingRenderWidgetHostViewBrowserTest() {}

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

  ~NoCompositingRenderWidgetHostViewBrowserTest() override {}

  bool SetUpSourceSurface(const char* wait_message) override {
    NOTIMPLEMENTED();
    return true;
  }
};

// Ensures that kBackForwardCache is always enabled to ensure that a new RWH is
// created on navigation.
class PaintHoldingRenderWidgetHostViewBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {
 public:
  PaintHoldingRenderWidgetHostViewBrowserTest() {
    scoped_feature_list_.InitWithFeaturesAndParameters(
        GetBasicBackForwardCacheFeatureForTesting(),
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

  ~PaintHoldingRenderWidgetHostViewBrowserTest() override = default;

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

// When creating the first RenderWidgetHostViewBase, the CompositorFrameSink can
// change. When this occurs we need to evict the current frame, and recreate
// surfaces. This tests that when frame eviction occurs while the
// RenderWidgetHostViewBase is visible, that we generate a new LocalSurfaceId.
// Simply invalidating can lead to displaying blank screens.
// (https://crbug.com/909903)
IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       ValidLocalSurfaceIdAfterInitialNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Creates the initial RenderWidgetHostViewBase, and connects to a
  // CompositorFrameSink. This will trigger frame eviction.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
  // Eviction normally invalidates the LocalSurfaceId, however if the
  // RenderWidgetHostViewBase is visible, a new id must be allocated. Otherwise
  // blank content is shown.
  EXPECT_TRUE(rwhvb);
  // Mac does not initialize RenderWidgetHostViewBase as visible.
#if !BUILDFLAG(IS_MAC)
  EXPECT_TRUE(rwhvb->IsShowing());
#endif
  EXPECT_TRUE(rwhvb->GetLocalSurfaceId().is_valid());
  // TODO(jonross): Unify FrameEvictor into RenderWidgetHostViewBase so that we
  // can generically test all eviction paths. However this should only be for
  // top level renderers. Currently the FrameEvict implementations are platform
  // dependent so we can't have a single generic test.
}

// Tests that when navigating to a new page the old page content continues to be
// shown until the new page content is ready or content rendering timeout fires.
IN_PROC_BROWSER_TEST_F(PaintHoldingRenderWidgetHostViewBrowserTest,
                       PaintHoldingOnNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Creates the initial RenderWidgetHostViewBase, and connects to a
  // CompositorFrameSink.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));

  RenderWidgetHostViewBase* first_view = GetRenderWidgetHostView();
  EXPECT_TRUE(first_view);
  viz::SurfaceId first_surface_id = first_view->GetCurrentSurfaceId();
  EXPECT_TRUE(first_surface_id.is_valid());

  // Perform a navigation to a new page. This will use a new render widget when
  // BackForwardCache is enabled.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_blur.html")));

  RenderWidgetHostViewBase* second_view = GetRenderWidgetHostView();
  EXPECT_TRUE(second_view);
  viz::SurfaceId second_surface_id = second_view->GetCurrentSurfaceId();
  EXPECT_TRUE(second_surface_id.is_valid());

  // After navigation there should be a new view with a different FrameSinkId.
  EXPECT_NE(first_view, second_view);
  EXPECT_NE(first_surface_id.frame_sink_id(),
            second_surface_id.frame_sink_id());

#if defined(USE_AURA)
  DelegatedFrameHost* dfh = static_cast<RenderWidgetHostViewAura*>(second_view)
                                ->GetDelegatedFrameHost();
  EXPECT_TRUE(dfh->HasPrimarySurface());
  EXPECT_TRUE(dfh->HasFallbackSurface());

  // The view after navigation should have a fallback SurfaceId that corresponds
  // to the SurfaceId from before navigation. This shows the old content after
  // navigation until either new content is ready or content rending timeout
  // fires.
  viz::SurfaceId fallback_surface_id = dfh->GetFallbackSurfaceIdForTesting();
  EXPECT_TRUE(first_surface_id.IsSameOrNewerThan(fallback_surface_id));
  EXPECT_NE(fallback_surface_id.frame_sink_id(),
            second_surface_id.frame_sink_id());
#endif

  // The render widget should have it's content rendering timeout timer after
  // navigating to the new page so the fallback content is eventually cleared.
  EXPECT_TRUE(GetRenderWidgetHost()->IsContentRenderingTimeoutRunning());
}

// TODO(jonross): Update Mac to also invalidate its viz::LocalSurfaceIds when
// performing navigations while hidden. https://crbug.com/935364
#if !BUILDFLAG(IS_MAC)
// When a navigation occurs while the RenderWidgetHostViewBase is hidden, it
// should invalidate it's viz::LocalSurfaceId. When subsequently being shown,
// a new surface should be generated with a new viz::LocalSurfaceId
IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       ValidLocalSurfaceIdAfterHiddenNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Creates the initial RenderWidgetHostViewBase, and connects to a
  // CompositorFrameSink.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
  EXPECT_TRUE(rwhvb);
  viz::LocalSurfaceId rwhvb_local_surface_id = rwhvb->GetLocalSurfaceId();
  EXPECT_TRUE(rwhvb_local_surface_id.is_valid());

  // Hide the view before performing the next navigation.
  shell()->web_contents()->WasHidden();
#if BUILDFLAG(IS_ANDROID)
  // On Android we want to ensure that we maintain the currently embedded
  // surface. So that there is something to display when returning to the tab.
  RenderWidgetHostViewAndroid* rwhva =
      static_cast<RenderWidgetHostViewAndroid*>(rwhvb);
  ui::DelegatedFrameHostAndroid* dfh =
      rwhva->delegated_frame_host_for_testing();
  EXPECT_TRUE(dfh->HasPrimarySurface());
  EXPECT_FALSE(dfh->IsPrimarySurfaceEvicted());
  viz::LocalSurfaceId initial_local_surface_id =
      dfh->SurfaceId().local_surface_id();
  EXPECT_TRUE(initial_local_surface_id.is_valid());
#endif

  // Perform a navigation to the same content source. This will reuse the
  // existing RenderWidgetHostViewBase, except if we trigger a RenderWidgetHost
  // swap on the navigation (due to RenderDocument).
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  rwhvb = GetRenderWidgetHostView();
  EXPECT_FALSE(rwhvb->GetLocalSurfaceId().is_valid());

#if BUILDFLAG(IS_ANDROID)
  // Navigating while hidden should not generate a new surface. As the old one
  // is maintained as the fallback. The DelegatedFrameHost should have not have
  // a valid active viz::LocalSurfaceId until the first surface after navigation
  // has been embedded.
  //
  // However, if the navigation involves a change of RenderFrameHosts the
  // surface will be evicted when committing the new RenderFrameHost (see also
  // ` DelegatedFrameHostAndroid::ClearFallbackSurfaceForCommitPending()`).
  rwhva = static_cast<RenderWidgetHostViewAndroid*>(rwhvb);
  dfh = rwhva->delegated_frame_host_for_testing();

  if (ShouldCreateNewHostForAllFrames()) {
    EXPECT_FALSE(dfh->HasPrimarySurface());
    EXPECT_TRUE(dfh->IsPrimarySurfaceEvicted());
    EXPECT_NE(initial_local_surface_id,
              dfh->content_layer()->surface_id().local_surface_id());
    EXPECT_TRUE(dfh->SurfaceId().local_surface_id().is_valid());
  } else {
    EXPECT_TRUE(dfh->HasPrimarySurface());
    EXPECT_FALSE(dfh->IsPrimarySurfaceEvicted());
    EXPECT_EQ(initial_local_surface_id,
              dfh->content_layer()->surface_id().local_surface_id());
    EXPECT_FALSE(dfh->SurfaceId().local_surface_id().is_valid());
  }
#endif

  // Showing the view should lead to a new surface being embedded.
  shell()->web_contents()->WasShown();
  viz::LocalSurfaceId new_rwhvb_local_surface_id = rwhvb->GetLocalSurfaceId();
  EXPECT_TRUE(new_rwhvb_local_surface_id.is_valid());
  EXPECT_NE(rwhvb_local_surface_id, new_rwhvb_local_surface_id);
#if BUILDFLAG(IS_ANDROID)
  EXPECT_TRUE(dfh->HasPrimarySurface());
  EXPECT_FALSE(dfh->IsPrimarySurfaceEvicted());
  viz::LocalSurfaceId new_local_surface_id =
      dfh->SurfaceId().local_surface_id();
  EXPECT_TRUE(new_local_surface_id.is_valid());
  EXPECT_NE(initial_local_surface_id, new_local_surface_id);
#endif
}

// Tests that if navigation fails, when re-using a RenderWidgetHostViewBase, and
// while it is hidden, that the fallback surface if invalidated. Then that when
// becoming visible, that a new valid surface is produced.
IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       NoFallbackAfterHiddenNavigationFails) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Creates the initial RenderWidgetHostViewBase, and connects to a
  // CompositorFrameSink.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
  ASSERT_TRUE(rwhvb);
  viz::LocalSurfaceId rwhvb_local_surface_id = rwhvb->GetLocalSurfaceId();
  EXPECT_TRUE(rwhvb_local_surface_id.is_valid());
  viz::SurfaceId initial_surface_id = rwhvb->GetCurrentSurfaceId();

  // Hide the view before performing the next navigation.
  shell()->web_contents()->WasHidden();
#if BUILDFLAG(IS_ANDROID)
  // On Android we want to ensure that we maintain the currently embedded
  // surface. So that there is something to display when returning to the tab.
  RenderWidgetHostViewAndroid* rwhva =
      static_cast<RenderWidgetHostViewAndroid*>(rwhvb);
  ui::DelegatedFrameHostAndroid* dfh =
      rwhva->delegated_frame_host_for_testing();
  EXPECT_TRUE(dfh->HasPrimarySurface());
  EXPECT_FALSE(dfh->IsPrimarySurfaceEvicted());
  viz::LocalSurfaceId initial_local_surface_id =
      dfh->SurfaceId().local_surface_id();
  EXPECT_TRUE(initial_local_surface_id.is_valid());
#endif

  // Perform a navigation to the same content source. This will reuse the
  // existing RenderWidgetHostViewBase, except if we trigger a RenderWidgetHost
  // swap on the navigation (due to RenderDocument).
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  rwhvb = GetRenderWidgetHostView();
  EXPECT_FALSE(rwhvb->GetLocalSurfaceId().is_valid());

  // Surface Synchronization can lead to several different Surfaces being
  // embedded during a navigation. Ending once the Browser and Renderer have
  // agreed to a set of VisualProperties.
  //
  // If this takes too long we hit a timeout that attempts to reset us back to
  // the initial surface. So that some content state can be presented.
  //
  // If a navigation were to fail and stayed in the same RenderFrameHost, then
  // this would be invoked before any new surface is embedded. For which we
  // expect it to clear out the fallback surfaces. As we cannot fallback to a
  // surface from before navigation.
  //
  // However, if the navigation involves a change of RenderFrameHosts (and thus
  // RenderWidgetViewHosts) we will embed a new surface early on when creating
  // the speculative RenderFrameHosts. This is OK because the surface is not
  // related to the previous page's surface, so we won't be showing the previous
  // page's content as a fallback.
  rwhvb->ResetFallbackToFirstNavigationSurface();
  if (ShouldCreateNewHostForAllFrames()) {
    EXPECT_TRUE(rwhvb->HasFallbackSurface());
    EXPECT_NE(rwhvb->GetFallbackSurfaceIdForTesting(), initial_surface_id);
  } else {
    EXPECT_FALSE(rwhvb->HasFallbackSurface());
  }

#if BUILDFLAG(IS_ANDROID)
  // Navigating while hidden should not generate a new surface.
  // The failed navigation above will lead to the primary surface being evicted.
  // The DelegatedFrameHost should have not have a valid active
  // viz::LocalSurfaceId until the first surface after navigation has been
  // embedded.
  rwhva = static_cast<RenderWidgetHostViewAndroid*>(rwhvb);
  dfh = rwhva->delegated_frame_host_for_testing();
  EXPECT_FALSE(dfh->HasPrimarySurface());
  EXPECT_TRUE(dfh->IsPrimarySurfaceEvicted());
  EXPECT_FALSE(dfh->content_layer()->surface_id().is_valid());
  // However, if the navigation involves a change of RenderFrameHosts (and thus
  // RenderWidgetViewHosts) a new surface is embedded (see comment a bit above).
  EXPECT_EQ(ShouldCreateNewHostForAllFrames(),
            dfh->SurfaceId().local_surface_id().is_valid());
#endif

  // Showing the view should lead to a new surface being embedded.
  shell()->web_contents()->WasShown();
  viz::LocalSurfaceId new_rwhvb_local_surface_id = rwhvb->GetLocalSurfaceId();
  EXPECT_TRUE(new_rwhvb_local_surface_id.is_valid());
  EXPECT_NE(rwhvb_local_surface_id, new_rwhvb_local_surface_id);
#if BUILDFLAG(IS_ANDROID)
  EXPECT_TRUE(dfh->HasPrimarySurface());
  EXPECT_FALSE(dfh->IsPrimarySurfaceEvicted());
  viz::LocalSurfaceId new_local_surface_id =
      dfh->SurfaceId().local_surface_id();
  EXPECT_TRUE(new_local_surface_id.is_valid());
  EXPECT_NE(initial_local_surface_id, new_local_surface_id);
#endif
}

#endif  // !BUILDFLAG(IS_MAC)

namespace {

#if BUILDFLAG(IS_ANDROID)
ui::DelegatedFrameHostAndroid* GetDelegatedFrameHost(
    RenderWidgetHostView* view) {
  return static_cast<RenderWidgetHostViewAndroid*>(view)
      ->delegated_frame_host_for_testing();
}
#else
DelegatedFrameHost* GetDelegatedFrameHost(RenderWidgetHostView* view) {
  DelegatedFrameHost* dfh = nullptr;
#if BUILDFLAG(IS_MAC)
  auto* compositor = GetBrowserCompositorMacForTesting(view);
  dfh = compositor->GetDelegatedFrameHost();
#elif BUILDFLAG(IS_IOS)
  auto* compositor = GetBrowserCompositorIOSForTesting(view);
  dfh = compositor->GetDelegatedFrameHost();
#elif defined(USE_AURA)
  dfh = static_cast<RenderWidgetHostViewAura*>(view)
            ->GetDelegatedFrameHostForTesting();
#endif
  return dfh;
}
#endif  // BUILDFLAG(IS_ANDROID)

viz::SurfaceId GetCurrentSurfaceIdOnDelegatedFrameHost(
    RenderWidgetHostView* view) {
  viz::SurfaceId surface_id;
#if BUILDFLAG(IS_ANDROID)
  ui::DelegatedFrameHostAndroid* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetCurrentSurfaceIdForTesting();
#else
  DelegatedFrameHost* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetCurrentSurfaceId();
#endif
  return surface_id;
}

viz::SurfaceId GetPreNavigationSurfaceIdOnDelegatedFrameHost(
    RenderWidgetHostView* view) {
  viz::SurfaceId surface_id;
#if BUILDFLAG(IS_ANDROID)
  ui::DelegatedFrameHostAndroid* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetPreNavigationSurfaceIdForTesting();
#else
  DelegatedFrameHost* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetPreNavigationSurfaceIdForTesting();
#endif
  return surface_id;
}

viz::SurfaceId GetFallbackSurfaceId(RenderWidgetHostView* view) {
  viz::SurfaceId surface_id;
#if BUILDFLAG(IS_ANDROID)
  ui::DelegatedFrameHostAndroid* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetFallbackSurfaceIdForTesting();
#else
  DelegatedFrameHost* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetFallbackSurfaceIdForTesting();
#endif
  return surface_id;
}

viz::SurfaceId GetBFCacheFallbackSurfaceId(RenderWidgetHostView* view) {
  viz::SurfaceId surface_id;
#if BUILDFLAG(IS_ANDROID)
  ui::DelegatedFrameHostAndroid* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetBFCacheFallbackSurfaceIdForTesting();
#else
  DelegatedFrameHost* dfh = GetDelegatedFrameHost(view);
  EXPECT_TRUE(dfh);
  surface_id = dfh->GetBFCacheFallbackSurfaceIdForTesting();
#endif
  return surface_id;
}

class BFCachedRenderWidgetHostViewBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {
 public:
  BFCachedRenderWidgetHostViewBrowserTest() {
    std::vector<base::test::FeatureRefAndParams> enabled_features =
        GetDefaultEnabledBackForwardCacheFeaturesForTesting(
            /*ignore_outstanding_network_request=*/false);

    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }
  ~BFCachedRenderWidgetHostViewBrowserTest() override = default;

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

IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCacheRestoredPageHasNewLocalSurfaceId) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  RenderFrameHostWrapper rfh1(shell()->web_contents()->GetPrimaryMainFrame());

#if BUILDFLAG(IS_OZONE)
  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
    // Process any pending platform resize/configure events to stabilize the
    // SurfaceId.
    viz::SurfaceId stable_id;
    viz::SurfaceId new_stable_id =
        GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
    while (new_stable_id != stable_id) {
      stable_id = new_stable_id;
      GiveItSomeTime();
      new_stable_id = GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
    }
  }
#endif

  const auto id_before_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_before_cached.is_valid());

  // Navitate to title2.html. Title1.html is in the BFCache.
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  const auto primary_id_for_title2 = GetCurrentSurfaceIdOnDelegatedFrameHost(
      shell()->web_contents()->GetPrimaryMainFrame()->GetView());

  ASSERT_TRUE(
      static_cast<RenderFrameHostImpl*>(rfh1.get())->IsInBackForwardCache());
  // `rfh1` is placed into BFCache. The LocalSurfaceId is preserved.
  const auto id_after_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_cached.is_valid());
  ASSERT_EQ(id_before_cached, id_after_cached);
  // We shouldn't have a pre navigation ID. This is only used temporarily to
  // preserve the page's primary ID before it enters BFCache. It is reset after
  // the page enters the BFCache.
  const auto pre_nav_id_after_cached =
      GetPreNavigationSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_FALSE(pre_nav_id_after_cached.is_valid());

  // Restore `rfh1` from BFCache.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));

  const auto id_after_restore =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_restore.is_valid());

  // - When `rfh1` navigates away,
  //   `RenderWidgetHostViewBase::DidNavigateMainFramePreCommit`
  //   preserves the current `LocalSurfaceId` on the `DelegatedFrameHost`.
  // - When `rfh1` is restored from BFCache, the View will call
  //   `DelegatedFrameHost::WasShown()` with a new `LocalSurfaceId`.
  ASSERT_TRUE(id_after_restore.IsNewerThan(id_after_cached));

  const auto fallback_after_restore = GetFallbackSurfaceId(rfh1->GetView());
  if (viz::FrameEvictionManager::GetInstance()->GetMaxNumberOfSavedFrames() >
      1u) {
    // The last primary ID after the page before it entered BFCache now serves
    // as the fallback surface.
    ASSERT_EQ(fallback_after_restore, id_after_cached);
  } else {
    // If we can only have one frame at a time, the navigation from title1.html
    // to title2.html will evict the surfaces of title1.html. When we restore
    // title1.html from the BFCache, it will take the primary ID of title2.html
    // as the fallback.
    ASSERT_EQ(fallback_after_restore, primary_id_for_title2.ToSmallestId());
  }
}

// TODO(crbug.com/345980824): This test is flaky on some Linux builders.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback \
  DISABLED_BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback
#else
#define MAYBE_BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback \
  BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback
#endif
// Same as the above test, except we resize the viewport while the page is in
// BFCache. The net effect is that we will NOT be using the last surface as
// the fallback for BFCache activation because resizing always regenerates a
// new ID as the fallback.
IN_PROC_BROWSER_TEST_F(
    BFCachedRenderWidgetHostViewBrowserTest,
    MAYBE_BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  RenderFrameHostWrapper rfh1(shell()->web_contents()->GetPrimaryMainFrame());

  const auto id_before_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_before_cached.is_valid());

  // Navitate to title2.html. Title1.html is in the BFCache.
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  ASSERT_TRUE(
      static_cast<RenderFrameHostImpl*>(rfh1.get())->IsInBackForwardCache());
  // `rfh1` is placed into BFCache. The LocalSurfaceId is preserved.
  const auto id_after_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_cached.is_valid());
  ASSERT_EQ(id_before_cached, id_after_cached);
  const auto pre_nav_id_after_cached =
      GetPreNavigationSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_FALSE(pre_nav_id_after_cached.is_valid());

  // Resize.
#if BUILDFLAG(IS_ANDROID)
  auto new_size = shell()
                      ->web_contents()
                      ->GetRenderWidgetHostView()
                      ->GetVisibleViewportSize();
  new_size.set_height(new_size.height() / 2);
  auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
  web_contents->GetNativeView()->OnSizeChanged(new_size.width(),
                                               new_size.height());
  web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(
      gfx::ScaleToCeiledSize(
          web_contents->GetNativeView()->GetPhysicalBackingSize(), 0.5f, 1));
#else
  auto view_bounds = shell()->web_contents()->GetViewBounds();
  view_bounds.set_height(view_bounds.height() / 2);
  shell()->web_contents()->Resize(view_bounds);
#endif

  // Restore `rfh1` from BFCache.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));

  // Resize has given us a newer ID.
  const auto id_after_restore =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_restore.is_valid());
  ASSERT_TRUE(id_after_restore.IsNewerThan(id_after_cached));

  const auto fallback_after_restore = GetFallbackSurfaceId(rfh1->GetView());

  // The fallback is equal to the primary ID after the restore. This is due to
  // the resizing.
  ASSERT_EQ(fallback_after_restore, id_after_restore);
}

// Same as above, except that the resize operation is a no-op.
IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCachedPageNoopResizedWhileHiddenHasPreservedFallback) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  RenderFrameHostWrapper rfh1(shell()->web_contents()->GetPrimaryMainFrame());

  const auto id_before_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_before_cached.is_valid());

  // Navitate to title2.html. Title1.html is in the BFCache.
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  const auto primary_id_for_title2 = GetCurrentSurfaceIdOnDelegatedFrameHost(
      shell()->web_contents()->GetPrimaryMainFrame()->GetView());

  ASSERT_TRUE(
      static_cast<RenderFrameHostImpl*>(rfh1.get())->IsInBackForwardCache());
  // `rfh1` is placed into BFCache. The LocalSurfaceId is preserved.
  const auto id_after_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_cached.is_valid());
  ASSERT_EQ(id_before_cached, id_after_cached);
  const auto pre_nav_id_after_cached =
      GetPreNavigationSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_FALSE(pre_nav_id_after_cached.is_valid());

  // No-op resize.
#if BUILDFLAG(IS_ANDROID)
  auto new_size = shell()
                      ->web_contents()
                      ->GetRenderWidgetHostView()
                      ->GetVisibleViewportSize();
  auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
  web_contents->GetNativeView()->OnSizeChanged(new_size.width(),
                                               new_size.height());
  web_contents->GetNativeView()->OnPhysicalBackingSizeChanged(
      web_contents->GetNativeView()->GetPhysicalBackingSize());
#else
  shell()->web_contents()->Resize(shell()->web_contents()->GetViewBounds());
#endif

  // Restore `rfh1` from BFCache.
  ASSERT_TRUE(HistoryGoBack(shell()->web_contents()));

  const auto id_after_restore =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_restore.is_valid());
  const auto fallback_after_restore = GetFallbackSurfaceId(rfh1->GetView());

  if (viz::FrameEvictionManager::GetInstance()->GetMaxNumberOfSavedFrames() >
      1u) {
    // The expectation is the same as if the no-op resize isn't called - the
    // last
    // primary ID after the page before it entered BFCache now serves as the
    // fallback surface.
    ASSERT_EQ(fallback_after_restore, id_after_cached);
  } else {
    // If we can only have one frame at a time, the navigation from title1.html
    // to title2.html will evict the surfaces of title1.html. When we restore
    // title1.html from the BFCache, it will take the primary ID of title2.html
    // as the fallback.
    ASSERT_EQ(fallback_after_restore, primary_id_for_title2.ToSmallestId());
  }
}

IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCachedViewShouldNotBeEvicted) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  RenderFrameHostWrapper rfh1(shell()->web_contents()->GetPrimaryMainFrame());

  const auto id_before_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_before_cached.is_valid());

  // Navitate to title2.html. Title1.html is in the BFCache.
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  ASSERT_TRUE(
      static_cast<RenderFrameHostImpl*>(rfh1.get())->IsInBackForwardCache());
  // `rfh1` is placed into BFCache. The LocalSurfaceId is preserved.
  const auto id_after_cached =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  ASSERT_TRUE(id_after_cached.is_valid());
  ASSERT_EQ(id_before_cached, id_after_cached);

  // If we only can save one frame - navigating to Title2.html will evict the
  // BFCached surface of title1. This should happen only on low memory devices,
  // or the memory pressure is high.
  if (viz::FrameEvictionManager::GetInstance()->GetMaxNumberOfSavedFrames() >
      1u) {
    // `ResetFallbackToFirstNavigationSurface()` should call
    // `RWHImpl::CollectSurfaceIdsForEviction()` which would mark the View as
    // evicted. It won't happen, however, because the View has entered BFCache
    // thus `ResetFallbackToFirstNavigationSurface()` won't be able to evict
    // anything.
    GetDelegatedFrameHost(rfh1->GetView())
        ->ResetFallbackToFirstNavigationSurface();
    ASSERT_FALSE(
        static_cast<RenderWidgetHostViewBase*>(rfh1->GetView())->is_evicted());

    // Even though `ResetFallbackToFirstNavigationSurface()` shouldn't evict
    // the BFCached surface, the surface should still be reachable via
    // `RWHImpl::CollectSurfaceIdsForEviction()`.
    //
    // Note: `RWHImpl::CollectSurfaceIdsForEviction()` has the side effect of
    // marking the View as evicted, so this assetion needs to be placed at the
    // end.
    const auto evicted_ids =
        static_cast<RenderWidgetHostImpl*>(rfh1->GetRenderWidgetHost())
            ->CollectSurfaceIdsForEviction();
    ASSERT_TRUE(std::ranges::contains(evicted_ids, id_after_cached));
  }
}

// Tests that if a page is evicted from BFCache, it notifies the
// DelegatedFrameHost{Android} to clear all the BFCached states.
IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       EvictedPageShouldNotifyDelegatedFrameHost) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
  RenderFrameHostWrapper rfh1(shell()->web_contents()->GetPrimaryMainFrame());
  const auto embedded_id =
      GetCurrentSurfaceIdOnDelegatedFrameHost(rfh1->GetView());
  EXPECT_TRUE(embedded_id.is_valid());

  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
  EXPECT_TRUE(
      static_cast<RenderFrameHostImpl*>(rfh1.get())->IsInBackForwardCache());
  // The fallback ID of the BFCached page should be equal to the embedded ID
  // before entering the BFCache.
  const auto bfcache_fallback = GetBFCacheFallbackSurfaceId(rfh1->GetView());
  EXPECT_TRUE(bfcache_fallback.is_valid());
  EXPECT_EQ(embedded_id, bfcache_fallback);

  const uint64_t reason = DisallowActivationReasonId::kForTesting;
  EXPECT_TRUE(rfh1->IsInactiveAndDisallowActivation(reason));
  const auto bfcache_fallback_after_eviction =
      GetBFCacheFallbackSurfaceId(rfh1->GetView());
  EXPECT_FALSE(bfcache_fallback_after_eviction.is_valid());
  EXPECT_EQ(bfcache_fallback_after_eviction.local_surface_id(),
            viz::LocalSurfaceId());
}

// Tests that if a pending commit attempts to swap from a RenderFrameHost which
// has no Fallback Surface, that we clear pre-existing ones in a
// RenderWidgetHostViewBase that is being re-used. While still properly
// allocating a new Surface if Navigation eventually succeeds.
IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       NoFallbackIfSwapFailedBeforeNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  // Creates the initial RenderWidgetHostViewBase, and connects to a
  // CompositorFrameSink.
  GURL url(embedded_test_server()->GetURL("/page_with_animation.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
  RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
  ASSERT_TRUE(rwhvb);
  viz::LocalSurfaceId initial_lsid = rwhvb->GetLocalSurfaceId();
  EXPECT_TRUE(initial_lsid.is_valid());

  // Actually set our Fallback Surface.
  rwhvb->ResetFallbackToFirstNavigationSurface();
  EXPECT_TRUE(rwhvb->HasFallbackSurface());

  // Perform a navigation to the same content source. This will reuse the
  // existing RenderWidgetHostViewBase, except if we trigger a RenderWidgetHost
  // swap on the navigation (due to RenderDocument).
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Actually complete a navigation once we've removed the Fallback Surface.
  // This should lead to a new viz::LocalSurfaceId.
  TestNavigationManager nav_manager(shell()->web_contents(), url);
  shell()->LoadURL(url);
  EXPECT_TRUE(nav_manager.WaitForResponse());
  // Notify that this pending commit has no RenderFrameHost with which to get a
  // Fallback Surface. This should evict the Fallback Surface.
  RenderFrameHostImpl* pending_rfh = static_cast<RenderFrameHostImpl*>(
      nav_manager.GetNavigationHandle()->GetRenderFrameHost());
  web_contents->NotifySwappedFromRenderManagerWithoutFallbackContent(
      pending_rfh);
  rwhvb = static_cast<RenderWidgetHostViewBase*>(pending_rfh->GetView());
  EXPECT_FALSE(rwhvb->HasFallbackSurface());
  EXPECT_TRUE(nav_manager.WaitForNavigationFinished());

  EXPECT_EQ(rwhvb, GetRenderWidgetHostView());
  EXPECT_TRUE(rwhvb->GetLocalSurfaceId().is_valid());
  viz::LocalSurfaceId post_nav_lsid = rwhvb->GetLocalSurfaceId();
  EXPECT_NE(initial_lsid, post_nav_lsid);

  // When RenderDocument is enabled, the RWHV will change as well so the
  // LocalSurfaceIds are not comparable.
  if (!ShouldCreateNewHostForAllFrames()) {
    EXPECT_TRUE(post_nav_lsid.IsNewerThan(initial_lsid));
  }
}

namespace {

std::unique_ptr<net::test_server::HttpResponse> HandleSlowStyleSheet(
    const net::test_server::HttpRequest& request) {
  // The CSS stylesheet we want to be slow will have this path.
  if (request.relative_url != "/slow-response")
    return nullptr;
  return std::make_unique<SlowHttpResponse>(SlowHttpResponse::NoResponse());
}

class FirstNonEmptyPaintObserver : public WebContentsObserver {
 public:
  explicit FirstNonEmptyPaintObserver(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}

  bool did_paint() const { return did_paint_; }

 private:
  // WebContentsObserver:
  void DidFirstVisuallyNonEmptyPaint() override { did_paint_ = true; }

  bool did_paint_{false};
};

}  // namespace

IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       ColorSchemeMetaBackground) {
  embedded_test_server()->RegisterRequestHandler(
      base::BindRepeating(&HandleSlowStyleSheet));
  ASSERT_TRUE(embedded_test_server()->Start());
  FirstNonEmptyPaintObserver observer(shell()->web_contents());
  TestNavigationObserver navigation(shell()->web_contents());
  shell()->LoadURL(
      embedded_test_server()->GetURL("/dark_color_scheme_meta_slow.html"));
  navigation.WaitForNavigationFinished();
  ASSERT_TRUE(
      WaitForDOMContentLoaded(shell()->web_contents()->GetPrimaryMainFrame()));
  EXPECT_FALSE(observer.did_paint());
  auto bg_color = GetRenderWidgetHostView()->content_background_color();
  ASSERT_TRUE(bg_color.has_value());
  EXPECT_EQ(SkColorSetRGB(18, 18, 18), bg_color.value());
}

IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       NoColorSchemeMetaBackground) {
  embedded_test_server()->RegisterRequestHandler(
      base::BindRepeating(&HandleSlowStyleSheet));
  ASSERT_TRUE(embedded_test_server()->Start());
  FirstNonEmptyPaintObserver observer(shell()->web_contents());
  TestNavigationObserver navigation(shell()->web_contents());
  shell()->LoadURL(
      embedded_test_server()->GetURL("/no_color_scheme_meta_slow.html"));
  navigation.WaitForNavigationFinished();
  ASSERT_TRUE(
      WaitForDOMContentLoaded(shell()->web_contents()->GetPrimaryMainFrame()));
  EXPECT_FALSE(observer.did_paint());
  auto bg_color = GetRenderWidgetHostView()->content_background_color();
  ASSERT_FALSE(bg_color.has_value());
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewBrowserTestBase,
                       CompositorWorksWhenReusingRenderer) {
  ASSERT_TRUE(embedded_test_server()->Start());
  auto* web_contents = shell()->web_contents();
  // Load a page that draws new frames infinitely.
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/page_with_animation.html")));
  std::unique_ptr<RenderFrameSubmissionObserver> frame_observer(
      std::make_unique<RenderFrameSubmissionObserver>(web_contents));

  // Open a new page in the same renderer to keep it alive.
  WebContents::CreateParams new_contents_params(
      web_contents->GetBrowserContext(), web_contents->GetSiteInstance());
  std::unique_ptr<WebContents> new_web_contents(
      WebContents::Create(new_contents_params));

  new_web_contents->GetController().LoadURLWithParams(
      NavigationController::LoadURLParams(
          embedded_test_server()->GetURL("/empty.html")));
  EXPECT_TRUE(WaitForLoadStop(new_web_contents.get()));

  // Start a cross-process navigation.
  shell()->LoadURL(embedded_test_server()->GetURL("foo.com", "/title1.html"));

  // When the navigation is about to commit, wait for the next frame to be
  // submitted by the renderer before proceeding with page load.
  {
    CommitBeforeSwapAckSentHelper commit_helper(web_contents,
                                                frame_observer.get());
    EXPECT_TRUE(WaitForLoadStop(web_contents));
    EXPECT_NE(web_contents->GetPrimaryMainFrame()->GetProcess(),
              new_web_contents->GetPrimaryMainFrame()->GetProcess());
  }

  // Go back and verify that the renderer continues to draw new frames.
  shell()->GoBackOrForward(-1);
  // Stop observing before we destroy |web_contents| in WaitForLoadStop.
  frame_observer.reset();
  EXPECT_TRUE(WaitForLoadStop(web_contents));
  EXPECT_EQ(web_contents->GetPrimaryMainFrame()->GetProcess(),
            new_web_contents->GetPrimaryMainFrame()->GetProcess());
  MainThreadFrameObserver observer(
      web_contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget());
  for (int i = 0; i < 5; ++i)
    observer.Wait();
}

enum CompositingMode {
  GL_COMPOSITING,
  SOFTWARE_COMPOSITING,
};

class CompositingRenderWidgetHostViewBrowserTest
    : public RenderWidgetHostViewBrowserTest,
      public testing::WithParamInterface<CompositingMode> {
 public:
  CompositingRenderWidgetHostViewBrowserTest()
      : compositing_mode_(GetParam()) {}

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

  void SetUp() override {
    if (compositing_mode_ == SOFTWARE_COMPOSITING)
      UseSoftwareCompositing();
    EnablePixelOutput(scale());
    RenderWidgetHostViewBrowserTest::SetUp();
  }

  virtual GURL TestUrl() {
    return net::FilePathToFileURL(
        test_dir().AppendASCII("rwhv_compositing_animation.html"));
  }

  bool SetUpSourceSurface(const char* wait_message) override {
    content::DOMMessageQueue message_queue(shell()->web_contents());
    EXPECT_TRUE(NavigateToURL(shell(), TestUrl()));
    if (wait_message != nullptr) {
      std::string result(wait_message);
      if (!message_queue.WaitForMessage(&result)) {
        EXPECT_TRUE(false) << "WaitForMessage " << result << " failed.";
        return false;
      }
    }

    // A frame might not be available yet. So, wait for it.
    WaitForCopySourceReady();
    return true;
  }

  virtual float scale() const { return 1.f; }

 private:
  const CompositingMode compositing_mode_;
};

// Disable tests for Android as it has an incomplete implementation.
#if !BUILDFLAG(IS_ANDROID)

// The CopyFromSurface() API should work on all platforms when compositing is
// enabled.
IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTest,
                       CopyFromSurface) {
  SET_UP_SURFACE_OR_PASS_TEST(nullptr);

  // Repeatedly call CopyFromSurface() since, on some platforms (e.g., Windows),
  // the operation will fail until the first "present" has been made.
  int count_attempts = 0;
  while (true) {
    ++count_attempts;
    base::RunLoop run_loop;
    GetRenderWidgetHostView()->CopyFromSurface(
        gfx::Rect(), frame_size(), base::TimeDelta(),
        base::BindOnce(&RenderWidgetHostViewBrowserTest::FinishCopyFromSurface,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();

    if (frames_captured())
      break;
    else
      GiveItSomeTime();
  }

  EXPECT_EQ(count_attempts, callback_invoke_count());
  EXPECT_EQ(1, frames_captured());
}

// Tests that the callback passed to CopyFromSurface is always called, even
// when the RenderWidgetHostView is deleting in the middle of an async copy.
IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTest,
                       CopyFromSurface_CallbackDespiteDelete) {
  SET_UP_SURFACE_OR_PASS_TEST(nullptr);

  base::RunLoop run_loop;
  GetRenderWidgetHostView()->CopyFromSurface(
      gfx::Rect(), frame_size(), base::TimeDelta(),
      base::BindOnce(&RenderWidgetHostViewBrowserTest::FinishCopyFromSurface,
                     base::Unretained(this), run_loop.QuitClosure()));
  shell()->web_contents()->Close();
  run_loop.Run();
  while (callback_invoke_count() == 0)
    GiveItSomeTime();

  EXPECT_EQ(1, callback_invoke_count());
}

class CompositingRenderWidgetHostViewBrowserTestTabCapture
    : public CompositingRenderWidgetHostViewBrowserTest {
 public:
  CompositingRenderWidgetHostViewBrowserTestTabCapture()
      : readback_result_(READBACK_NO_RESPONSE),
        allowable_error_(0),
        test_url_("data:text/html,<!doctype html>") {}

  void VerifyResult(base::OnceClosure quit_callback,
                    const content::CopyFromSurfaceResult& result) {
    const SkBitmap& bitmap = result.has_value() ? result->bitmap : SkBitmap();
    if (bitmap.drawsNothing()) {
      readback_result_ = READBACK_FAILED;
      std::move(quit_callback).Run();
      return;
    }
    readback_result_ = READBACK_SUCCESS;

    // Check that the |bitmap| contains cyan and/or yellow pixels.  This is
    // needed because the compositor will read back "blank" frames until the
    // first frame from the renderer is composited.  See comments in
    // PerformTestWithLeftRightRects() for more details about eliminating test
    // flakiness.
    bool contains_a_test_color = false;
    for (int i = 0; i < bitmap.width(); ++i) {
      for (int j = 0; j < bitmap.height(); ++j) {
        if (!exclude_rect_.IsEmpty() && exclude_rect_.Contains(i, j))
          continue;

        const unsigned high_threshold = 0xff - allowable_error_;
        const unsigned low_threshold = 0x00 + allowable_error_;
        const SkColor color = bitmap.getColor(i, j);
        const bool is_cyan = SkColorGetR(color) <= low_threshold &&
                             SkColorGetG(color) >= high_threshold &&
                             SkColorGetB(color) >= high_threshold;
        const bool is_yellow = SkColorGetR(color) >= high_threshold &&
                               SkColorGetG(color) >= high_threshold &&
                               SkColorGetB(color) <= low_threshold;
        if (is_cyan || is_yellow) {
          contains_a_test_color = true;
          break;
        }
      }
    }
    if (!contains_a_test_color) {
      readback_result_ = READBACK_NO_TEST_COLORS;
      std::move(quit_callback).Run();
      return;
    }

    // Compare the readback |bitmap| to the |expected_bitmap|, pixel-by-pixel.
    const SkBitmap& expected_bitmap =
        expected_copy_from_compositing_surface_bitmap_;
    EXPECT_EQ(expected_bitmap.width(), bitmap.width());
    EXPECT_EQ(expected_bitmap.height(), bitmap.height());
    if (expected_bitmap.width() != bitmap.width() ||
        expected_bitmap.height() != bitmap.height()) {
      readback_result_ = READBACK_INCORRECT_RESULT_SIZE;
      std::move(quit_callback).Run();
      return;
    }
    EXPECT_EQ(expected_bitmap.colorType(), bitmap.colorType());
    int fails = 0;
    // Note: The outermost 2 pixels are ignored because the scaling tests pick
    // up a little bleed-in from the surrounding content.
    for (int i = 2; i < bitmap.width() - 4 && fails < 10; ++i) {
      for (int j = 2; j < bitmap.height() - 4 && fails < 10; ++j) {
        if (!exclude_rect_.IsEmpty() && exclude_rect_.Contains(i, j))
          continue;

        SkColor expected_color = expected_bitmap.getColor(i, j);
        SkColor color = bitmap.getColor(i, j);
        int expected_alpha = SkColorGetA(expected_color);
        int alpha = SkColorGetA(color);
        int expected_red = SkColorGetR(expected_color);
        int red = SkColorGetR(color);
        int expected_green = SkColorGetG(expected_color);
        int green = SkColorGetG(color);
        int expected_blue = SkColorGetB(expected_color);
        int blue = SkColorGetB(color);
        EXPECT_NEAR(expected_alpha, alpha, allowable_error_)
            << "expected_color: " << std::hex << expected_color
            << " color: " <<  color
            << " Failed at " << std::dec << i << ", " << j
            << " Failure " << ++fails;
        EXPECT_NEAR(expected_red, red, allowable_error_)
            << "expected_color: " << std::hex << expected_color
            << " color: " <<  color
            << " Failed at " << std::dec << i << ", " << j
            << " Failure " << ++fails;
        EXPECT_NEAR(expected_green, green, allowable_error_)
            << "expected_color: " << std::hex << expected_color
            << " color: " <<  color
            << " Failed at " << std::dec << i << ", " << j
            << " Failure " << ++fails;
        EXPECT_NEAR(expected_blue, blue, allowable_error_)
            << "expected_color: " << std::hex << expected_color
            << " color: " <<  color
            << " Failed at " << std::dec << i << ", " << j
            << " Failure " << ++fails;
      }
    }
    EXPECT_LT(fails, 10);

    std::move(quit_callback).Run();
  }

  void SetAllowableError(int amount) { allowable_error_ = amount; }
  void SetExcludeRect(gfx::Rect exclude) { exclude_rect_ = exclude; }

  GURL TestUrl() override { return GURL(test_url_); }

  void SetTestUrl(const std::string& url) { test_url_ = url; }

  // Loads a page two boxes side-by-side, each half the width of
  // |html_rect_size|, and with different background colors. The test then
  // copies from |copy_rect| region of the page into a bitmap of size
  // |output_size|, and examines the resulting bitmap.
  // Note that |output_size| may not have the same size as |copy_rect| (e.g.
  // when the output is scaled).
  void PerformTestWithLeftRightRects(const gfx::Size& html_rect_size,
                                     const gfx::Rect& copy_rect,
                                     const gfx::Size& output_size) {
    const gfx::Size box_size(html_rect_size.width() / 2,
                             html_rect_size.height());
    SetTestUrl(base::StringPrintf(
        "data:text/html,<!doctype html>"
        "<div class='left'>"
        "  <div class='right'></div>"
        "</div>"
        "<style>"
        "body { padding: 0; margin: 0; }"
        ".left { position: absolute;"
        "        background: %%230ff;"
        "        width: %dpx;"
        "        height: %dpx;"
        "}"
        ".right { position: absolute;"
        "         left: %dpx;"
        "         background: %%23ff0;"
        "         width: %dpx;"
        "         height: %dpx;"
        "}"
        "</style>"
        "<script>"
        "  domAutomationController.send(\"DONE\");"
        "</script>",
        box_size.width(),
        box_size.height(),
        box_size.width(),
        box_size.width(),
        box_size.height()));

    SET_UP_SURFACE_OR_PASS_TEST("\"DONE\"");
    if (!ShouldContinueAfterTestURLLoad())
      return;

    RenderWidgetHostViewBase* rwhv = GetRenderWidgetHostView();

    SetupLeftRightBitmap(output_size,
                         &expected_copy_from_compositing_surface_bitmap_);

    // The page is loaded in the renderer.  Request frames from the renderer
    // until readback succeeds.  When readback succeeds, the resulting
    // SkBitmap is examined to ensure it matches the expected result.
    // This loop is needed because:
    //   1. Painting/Compositing is not synchronous with the Javascript engine,
    //      and so the "DONE" signal above could be received before the renderer
    //      provides a frame with the expected content.  http://crbug.com/405282
    //   2. Avoiding test flakiness: On some platforms, the readback operation
    //      is allowed to transiently fail.  The purpose of these tests is to
    //      confirm correct cropping/scaling behavior; and not that every
    //      readback must succeed.  http://crbug.com/444237
    int attempt_count = 0;
    do {
      // Wait a little before retrying again. This gives the most up-to-date
      // frame a chance to propagate from the renderer to the compositor.
      if (attempt_count > 0)
        GiveItSomeTime();
      ++attempt_count;

      // Request readback.  The callbacks will examine the pixels in the
      // SkBitmap result if readback was successful.
      readback_result_ = READBACK_NO_RESPONSE;
      SetAllowableError(2);
      // Scaling can cause blur/fuzz between color boundaries, particularly in
      // the middle columns for these tests.
      SetExcludeRect(
          gfx::Rect(output_size.width() / 2 - 1, 0, 2, output_size.height()));

      base::RunLoop run_loop;
      rwhv->CopyFromSurface(
          copy_rect, output_size, base::TimeDelta(),
          base::BindOnce(&CompositingRenderWidgetHostViewBrowserTestTabCapture::
                             VerifyResult,
                         base::Unretained(this), run_loop.QuitClosure()));
      run_loop.Run();

      // If the readback operation did not provide a frame, log the reason
      // to aid in future debugging.  This information will also help determine
      // whether the implementation is broken, or a test bot is in a bad state.
      // clang-format off
      switch (readback_result_) {
        case READBACK_SUCCESS:
          break;
        #define CASE_LOG_READBACK_WARNING(enum_value)                    \
          case enum_value:                                               \
            LOG(WARNING) << "Readback attempt failed (attempt #"         \
                         << attempt_count << ").  Reason: " #enum_value; \
            break
        CASE_LOG_READBACK_WARNING(READBACK_FAILED);
        CASE_LOG_READBACK_WARNING(READBACK_NO_TEST_COLORS);
        CASE_LOG_READBACK_WARNING(READBACK_INCORRECT_RESULT_SIZE);
        default:
          NOTREACHED()
              << "Invalid readback response value: " << readback_result_;
      }
      // clang-format on
    } while (readback_result_ != READBACK_SUCCESS &&
             !testing::Test::HasFailure());
  }

  // Sets up |bitmap| to have size |copy_size|. It floods the left half with
  // #0ff and the right half with #ff0.
  void SetupLeftRightBitmap(const gfx::Size& copy_size, SkBitmap* bitmap) {
    bitmap->allocN32Pixels(copy_size.width(), copy_size.height());
    // Left half is #0ff.
    bitmap->eraseARGB(255, 0, 255, 255);
    // Right half is #ff0.
    for (int i = 0; i < copy_size.width() / 2; ++i) {
      for (int j = 0; j < copy_size.height(); ++j) {
        *(bitmap->getAddr32(copy_size.width() / 2 + i, j)) =
            SkColorSetARGB(255, 255, 255, 0);
      }
    }
  }

 protected:
  // An enum to distinguish between reasons for result verify failures.
  enum ReadbackResult {
    READBACK_NO_RESPONSE,
    READBACK_SUCCESS,
    READBACK_FAILED,
    READBACK_NO_TEST_COLORS,
    READBACK_INCORRECT_RESULT_SIZE,
  };

  virtual bool ShouldContinueAfterTestURLLoad() {
    return true;
  }

 private:
  ReadbackResult readback_result_;
  SkBitmap expected_copy_from_compositing_surface_bitmap_;
  int allowable_error_;
  gfx::Rect exclude_rect_;
  std::string test_url_;
};

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Origin_Unscaled) {
  gfx::Rect copy_rect(400, 300);
  gfx::Size output_size = copy_rect.size();
  gfx::Size html_rect_size(400, 300);
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Origin_Scaled) {
  gfx::Rect copy_rect(400, 300);
  gfx::Size output_size(200, 100);
  gfx::Size html_rect_size(400, 300);
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Cropped_Unscaled) {
  // Grab 60x60 pixels from the center of the tab contents.
  gfx::Rect copy_rect(400, 300);
  copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30),
                        gfx::Size(60, 60));
  gfx::Size output_size = copy_rect.size();
  gfx::Size html_rect_size(400, 300);
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Cropped_Scaled) {
  // Grab 60x60 pixels from the center of the tab contents.
  gfx::Rect copy_rect(400, 300);
  copy_rect = gfx::Rect(copy_rect.CenterPoint() - gfx::Vector2d(30, 30),
                        gfx::Size(60, 60));
  gfx::Size output_size(20, 10);
  gfx::Size html_rect_size(400, 300);
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

class CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI
    : public CompositingRenderWidgetHostViewBrowserTestTabCapture {
 public:
  CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI() {}

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

 protected:
  bool ShouldContinueAfterTestURLLoad() override {
    // Short-circuit a pass for platforms where setting up high-DPI fails.
    const float actual_scale_factor =
        GetScaleFactorForView(GetRenderWidgetHostView());
    if (actual_scale_factor != scale()) {
      LOG(WARNING) << "Blindly passing this test; unable to force device scale "
                   << "factor: seems to be " << actual_scale_factor
                   << " but expected " << scale();
      return false;
    }
    VLOG(1) << ("Successfully forced device scale factor.  Moving forward with "
                "this test!  :-)");
    return true;
  }

  float scale() const override { return 2.0f; }
};

// NineImagePainter implementation crashes the process on Windows when this
// content_browsertest forces a device scale factor.  http://crbug.com/399349
#if BUILDFLAG(IS_WIN)
#define MAYBE_CopyToBitmap_EntireRegion DISABLED_CopyToBitmap_EntireRegion
#define MAYBE_CopyToBitmap_CenterRegion DISABLED_CopyToBitmap_CenterRegion
#define MAYBE_CopyToBitmap_ScaledResult DISABLED_CopyToBitmap_ScaledResult
#else
#define MAYBE_CopyToBitmap_EntireRegion CopyToBitmap_EntireRegion
#define MAYBE_CopyToBitmap_CenterRegion CopyToBitmap_CenterRegion
#define MAYBE_CopyToBitmap_ScaledResult CopyToBitmap_ScaledResult
#endif

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_EntireRegion) {
  gfx::Size html_rect_size(200, 150);
  gfx::Rect copy_rect(200, 150);
  // Scale the output size so that, internally, scaling is not occurring.
  gfx::Size output_size = gfx::ScaleToRoundedSize(copy_rect.size(), scale());
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_CenterRegion) {
  gfx::Size html_rect_size(200, 150);
  // Grab 90x60 pixels from the center of the tab contents.
  gfx::Rect copy_rect =
      gfx::Rect(gfx::Rect(html_rect_size).CenterPoint() - gfx::Vector2d(45, 30),
                gfx::Size(90, 60));
  // Scale the output size so that, internally, scaling is not occurring.
  gfx::Size output_size = gfx::ScaleToRoundedSize(copy_rect.size(), scale());
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_ScaledResult) {
  gfx::Size html_rect_size(200, 100);
  gfx::Rect copy_rect(200, 100);
  // Output is being down-scaled since output_size is in phyiscal pixels.
  gfx::Size output_size(200, 100);
  PerformTestWithLeftRightRects(html_rect_size, copy_rect, output_size);
}

#if BUILDFLAG(IS_CHROMEOS)
// On ChromeOS there is no software compositing.
static const auto kTestCompositingModes = testing::Values(GL_COMPOSITING);
#else
static const auto kTestCompositingModes =
    testing::Values(GL_COMPOSITING, SOFTWARE_COMPOSITING);
#endif

INSTANTIATE_TEST_SUITE_P(GLAndSoftwareCompositing,
                         CompositingRenderWidgetHostViewBrowserTest,
                         kTestCompositingModes);
INSTANTIATE_TEST_SUITE_P(GLAndSoftwareCompositing,
                         CompositingRenderWidgetHostViewBrowserTestTabCapture,
                         kTestCompositingModes);
INSTANTIATE_TEST_SUITE_P(
    GLAndSoftwareCompositing,
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    kTestCompositingModes);

class RenderWidgetHostViewPresentationFeedbackBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {
 public:
  RenderWidgetHostViewPresentationFeedbackBrowserTest(
      const RenderWidgetHostViewPresentationFeedbackBrowserTest&) = delete;
  RenderWidgetHostViewPresentationFeedbackBrowserTest& operator=(
      const RenderWidgetHostViewPresentationFeedbackBrowserTest&) = delete;

 protected:
  using TabSwitchResult = blink::ContentToVisibleTimeReporter::TabSwitchResult;

  RenderWidgetHostViewPresentationFeedbackBrowserTest() = default;

  ~RenderWidgetHostViewPresentationFeedbackBrowserTest() override = default;

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

    ASSERT_TRUE(embedded_test_server()->Start());
    ASSERT_TRUE(NavigateToURL(
        shell(), embedded_test_server()->GetURL("/page_with_animation.html")));

    RenderWidgetHostViewBase* rwhvb = GetRenderWidgetHostView();
    ASSERT_TRUE(rwhvb);

    // Start with the widget hidden.
    rwhvb->Hide();

#if BUILDFLAG(IS_MAC)
    // On Mac, DelegatedFrameHost only behaves the same as on other platforms
    // when it has no parent UI layer.
    ASSERT_FALSE(
        GetBrowserCompositor()->GetDelegatedFrameHostLayer()->parent());
#endif
  }

  // Set a VisibleTimeRequest that will be sent the first time the widget
  // becomes visible. The default parameters request a tab switch measurement.
  void CreateVisibleTimeRequest(bool show_reason_tab_switching = true,
                                bool show_reason_bfcache_restore = false) {
    auto& request_trigger =
        GetRenderWidgetHostView()->host()->GetVisibleTimeRequestTrigger();
    if (show_reason_tab_switching) {
      request_trigger.UpdateRequest(blink::VisibleTimeEvent{
          .event_start_time = base::TimeTicks::Now(),
          .reason = blink::VisibleTimeEvent::TabSwitchReason{
              .destination_is_loaded = true,
              .had_saved_frame_at_start =
                  GetRenderWidgetHostView()->HasSavedCompositorFrame(),
          }});
    }
    if (show_reason_bfcache_restore) {
      GetRenderWidgetHostView()->OnOldViewDidNavigatePreCommit();
      GetRenderWidgetHostView()->DidEnterBackForwardCache();
      request_trigger.UpdateRequest(blink::VisibleTimeEvent{
          .event_start_time = base::TimeTicks::Now(),
          .reason = blink::VisibleTimeEvent::BFCacheRestoreReason{}});
    }
  }

  void ExpectPresentationFeedback(TabSwitchResult expected_result) {
    // Wait for the expected result (only) to be logged.
    const base::TimeTicks start_time = base::TimeTicks::Now();
    while (histogram_tester_.GetAllSamples("Browser.Tabs.TabSwitchResult3")
               .empty()) {
      ASSERT_LT(base::TimeTicks::Now() - start_time,
                TestTimeouts::action_timeout())
          << "Timed out waiting for Browser.Tabs.TabSwitchResult3.";
      GiveItSomeTime();
    }
    histogram_tester_.ExpectUniqueSample("Browser.Tabs.TabSwitchResult3",
                                         expected_result, 1);
  }

  void ExpectNoPresentationFeedback() {
    const base::TimeTicks start_time = base::TimeTicks::Now();
    // The full action_timeout is excessively long when expecting nothing to be
    // logged.
    const base::TimeDelta kTimeout = TestTimeouts::action_timeout() / 10;
    while (base::TimeTicks::Now() - start_time < kTimeout) {
      GiveItSomeTime();
      ASSERT_TRUE(
          histogram_tester_.GetAllSamples("Browser.Tabs.TabSwitchResult3")
              .empty());
    }
  }

#if BUILDFLAG(IS_MAC)
  // Helpers for parent layer tests.

  // Holds a ui::Layer with its own compositor to be set as parent layer during
  // tests. This must be destroyed before tearing down the test harness so that
  // the ContentBrowserTest environment doesn't have any references to the
  // ui::Layer during destruction.
  class ScopedParentLayer {
   public:
    ScopedParentLayer(BrowserCompositorMac* browser_compositor)
        : browser_compositor_(browser_compositor) {
      recyclable_compositor_ = std::make_unique<ui::RecyclableCompositorMac>(
          content::GetContextFactory());
      ui::LayerTestApi(&layer_).SetCompositor(
          recyclable_compositor_->compositor());
    }

    ~ScopedParentLayer() {
      browser_compositor_->SetParentUiLayer(nullptr);
      layer_.ResetCompositor();
      recyclable_compositor_.reset();
    }

    ui::Layer* layer() { return &layer_; }

   private:
    raw_ptr<BrowserCompositorMac> browser_compositor_;
    ui::LayerSolidColor layer_;
    std::unique_ptr<ui::RecyclableCompositorMac> recyclable_compositor_;
  };

  BrowserCompositorMac* GetBrowserCompositor() const {
    return GetBrowserCompositorMacForTesting(GetRenderWidgetHostView());
  }
#endif

  base::HistogramTester histogram_tester_;
};

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       Show) {
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  ExpectPresentationFeedback(TabSwitchResult::kSuccess);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowThenHide) {
  // An incomplete tab switch is logged when the widget is hidden before
  // presenting a frame.
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  GetRenderWidgetHostView()->Hide();
  ExpectPresentationFeedback(TabSwitchResult::kIncomplete);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       HiddenButPainting) {
  // Browser.Tabs.* is not logged if the page becomes "visible" due to a hidden
  // capturer.
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(
      PageVisibilityState::kHiddenButPainting);
  ExpectNoPresentationFeedback();
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowWhileCapturing) {
  // Frame is captured and then becomes visible.
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(
      PageVisibilityState::kHiddenButPainting);
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  ExpectPresentationFeedback(TabSwitchResult::kSuccess);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       HideWhileCapturing) {
  // Capture starts and frame becomes "hidden" before a render frame is
  // presented.
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  GetRenderWidgetHostView()->ShowWithVisibility(
      PageVisibilityState::kHiddenButPainting);
  ExpectPresentationFeedback(TabSwitchResult::kIncomplete);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowWithoutTabSwitchRequest) {
  CreateVisibleTimeRequest(/*show_reason_tab_switching=*/false,
                           /*show_reason_bfcache_restore=*/true);
  // Browser.Tabs.* is not logged if not requested.
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  ExpectNoPresentationFeedback();
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowThenHideWithoutTabSwitchRequest) {
  CreateVisibleTimeRequest(/*show_reason_tab_switching=*/false,
                           /*show_reason_bfcache_restore=*/true);
  // Browser.Tabs.* is not logged if not requested.
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  GetRenderWidgetHostView()->Hide();
  ExpectNoPresentationFeedback();
}

#if BUILDFLAG(IS_MAC)

// The default tests do not set a parent UI layer, so the BrowserCompositorMac
// state is always HasNoCompositor when the RWHV is hidden, or HasOwnCompositor
// when the RWHV is visible. These tests add a parent layer to make sure that
// presentation feedback is logged when the state is UseParentLayerCompositor.

// TODO(crbug.com/40163556): These tests don't match the behaviour of the
// browser. In production the Browser.Tabs.* histograms are logged but in this
// test, the presentation time request is swallowed during the
// UseParentLayerCompositor state. Need to find out what's wrong with the test
// setup.

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       DISABLED_ShowWithParentLayer) {
  CreateVisibleTimeRequest();
  ScopedParentLayer parent_layer(GetBrowserCompositor());
  GetBrowserCompositor()->SetParentUiLayer(parent_layer.layer());
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  ExpectPresentationFeedback(TabSwitchResult::kSuccess);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       DISABLED_ShowThenAddParentLayer) {
  CreateVisibleTimeRequest();
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  ScopedParentLayer parent_layer(GetBrowserCompositor());
  GetBrowserCompositor()->SetParentUiLayer(parent_layer.layer());
  ExpectPresentationFeedback(TabSwitchResult::kSuccess);
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       DISABLED_ShowThenRemoveParentLayer) {
  CreateVisibleTimeRequest();
  ScopedParentLayer parent_layer(GetBrowserCompositor());
  GetBrowserCompositor()->SetParentUiLayer(parent_layer.layer());
  GetRenderWidgetHostView()->ShowWithVisibility(PageVisibilityState::kVisible);
  GetBrowserCompositor()->SetParentUiLayer(nullptr);
  ExpectPresentationFeedback(TabSwitchResult::kSuccess);
}

#endif  // BUILDFLAG(IS_MAC)

#endif  // !BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_ANDROID)
void CheckSurfaceRangeRemovedAfterCopy(
    viz::SurfaceRange range,
    CompositorImpl* compositor,
    base::RepeatingClosure resume_test,
    const content::CopyFromSurfaceResult& result) {
  // The surface range is removed first when the browser receives the result
  // of the copy request. Then the result callback (including this function) is
  // run.
  auto iter =
      compositor->GetLayerTreeForTesting()->GetSurfaceRangesForTesting().find(
          range);
  ASSERT_NE(
      iter,
      compositor->GetLayerTreeForTesting()->GetSurfaceRangesForTesting().end());
  // In DelegatedFrameHostAndroid we keep an extra ref for visible surfaces to
  // make sure tab capture works, so this should be 1, not 0.
  EXPECT_EQ(iter->second, 1);
  std::move(resume_test).Run();
}

class RenderWidgetHostViewCopyFromSurfaceBrowserTest
    : public RenderWidgetHostViewBrowserTest {
 public:
  RenderWidgetHostViewCopyFromSurfaceBrowserTest() {
    // Enable `RenderDocument` to guarantee renderer/RFH swap for cross-site
    // navigations.
    InitAndEnableRenderDocumentFeature(&scoped_feature_list_render_document_,
                                       RenderDocumentFeatureFullyEnabled()[0]);
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->ServeFilesFromSourceDirectory(
        GetTestDataFilePath());
    net::test_server::RegisterDefaultHandlers(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
    RenderWidgetHostViewBrowserTest::SetUpOnMainThread();
  }

  ~RenderWidgetHostViewCopyFromSurfaceBrowserTest() override = default;

  bool SetUpSourceSurface(const char* wait_message) override { return false; }

 private:
  base::test::ScopedFeatureList scoped_feature_list_render_document_;
};

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewCopyFromSurfaceBrowserTest,
                       AsyncCopyFromSurface) {
  EXPECT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));

  auto* rwhv_android = static_cast<RenderWidgetHostViewAndroid*>(
      GetRenderViewHost()->GetWidget()->GetView());
  auto* compositor = static_cast<CompositorImpl*>(
      rwhv_android->GetNativeView()->GetWindowAndroid()->GetCompositor());

  const viz::SurfaceRange range_for_copy(rwhv_android->GetCurrentSurfaceId(),
                                         rwhv_android->GetCurrentSurfaceId());
  const viz::SurfaceRange range_for_mainframe(
      std::nullopt, rwhv_android->GetCurrentSurfaceId());
  base::RunLoop run_loop;
  GetRenderViewHost()->GetWidget()->GetView()->CopyFromSurface(
      gfx::Rect(), gfx::Size(), base::TimeDelta(),
      base::BindOnce(&CheckSurfaceRangeRemovedAfterCopy, range_for_copy,
                     compositor, run_loop.QuitClosure()));

  // In DelegatedFrameHostAndroid we keep an extra ref for visible
  // surfaces to make sure tab capture works.
  EXPECT_THAT(
      compositor->GetLayerTreeForTesting()->GetSurfaceRangesForTesting(),
      testing::UnorderedElementsAre(std::make_pair(range_for_copy, 2),
                                    std::make_pair(range_for_mainframe, 1)));
  run_loop.Run(FROM_HERE);
}

namespace {

void AssertCopySharedImageSucceeded(
    base::RepeatingClosure resume_test,
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    viz::ReleaseCallback release_callback) {
  EXPECT_TRUE(shared_image);
  if (release_callback) {
    std::move(release_callback).Run(gpu::SyncToken(), /*is_lost=*/false);
  }
  std::move(resume_test).Run();
}

class ScopedSnapshotWaiter : public WebContentsObserver {
 public:
  ScopedSnapshotWaiter(WebContents* wc, const GURL& destination)
      : WebContentsObserver(wc), destination_(destination) {}

  ScopedSnapshotWaiter(const ScopedSnapshotWaiter&) = delete;
  ScopedSnapshotWaiter& operator=(const ScopedSnapshotWaiter&) = delete;
  ~ScopedSnapshotWaiter() override = default;

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

 private:
  void DidStartNavigation(NavigationHandle* handle) override {
    if (handle->GetURL() != destination_) {
      return;
    }

    auto* request = NavigationRequest::From(handle);
    request->set_ready_to_commit_callback_for_testing(base::BindOnce(
        [](RenderWidgetHostView* old_view,
           base::OnceCallback<bool()> renderer_swapped,
           base::RepeatingClosure resume) {
          ASSERT_TRUE(std::move(renderer_swapped).Run());
          ASSERT_TRUE(old_view);
          static_cast<RenderWidgetHostViewAndroid*>(old_view)
              ->CopySharedImageFromExactSurface(
                  gfx::Rect(), gfx::Size(),
                  base::BindOnce(&AssertCopySharedImageSucceeded,
                                 std::move(resume)));
        },
        request->frame_tree_node()->current_frame_host()->GetView(),
        // The request must outlive its own callback.
        base::BindOnce(
            base::BindLambdaForTesting([](NavigationRequest* request) {
              return request->GetRenderFrameHost() !=
                     request->frame_tree_node()
                         ->render_manager()
                         ->current_frame_host();
            }),
            base::Unretained(request)),
        run_loop_.QuitClosure()));
  }

  const GURL destination_;
  base::RunLoop run_loop_;
};
}  // namespace

// A "best effort" browser test: issue an exact `CopyOutputRequest` during a
// cross-renderer navigation, when the navigation is about to commit in the
// browser. We should always be able to get a desired snapshot back.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewCopyFromSurfaceBrowserTest,
                       CopyExactSurfaceDuringCrossRendererNavigations) {
  ASSERT_TRUE(
      NavigateToURL(shell()->web_contents(),
                    embedded_test_server()->GetURL("a.com", "/empty.html")));
  // Makes sure "empty.html" is in a steady state and ready to be copied.
  WaitForCopyableViewInWebContents(shell()->web_contents());

  const auto cross_renderer_url =
      embedded_test_server()->GetURL("b.com", "/title1.html");
  ScopedSnapshotWaiter waiter(shell()->web_contents(), cross_renderer_url);
  ASSERT_TRUE(NavigateToURL(shell()->web_contents(), cross_renderer_url));
  // Force the new renderer for "title1.html" to submit a new compositor frame
  // and ack by viz, such that our `CopyOutputRequest` is fulfilled.
  WaitForCopyableViewInWebContents(shell()->web_contents());
  // Blocks until we get the desired snapshot of "empty.html".
  waiter.Wait();
}
#endif

namespace {

// When an OOPIF performs a "location.replace" main frame navigation and with
// BFCache enabled, it can lead to a redundant `ui::ViewAndroid` attached under
// `WebContentsViewAndroid` (*), even though the OOPIF and its embedding main
// frame are stored in BFCache, the redundant `ui::ViewAndroid` is not detached
// properly.
//
// *: Also the case for Aura with duplicated `ui::Window`; unclear about other
//    platforms.
//
// The root cause:
// 1. The "location.replace" first signals the browser that we will not swap the
//    BrowsingInstance.
// 2. Since BI is not swapped (yet), the speculative RFH can be in the same
//    SiteInstanceGroup as the RFH of the OOPIF (i.e., both being b.com). Same
//    SIGroup means the speculative RFH and the OOPIF RFH ref-count the same
//    `RenderViewHost`.
// 3. And since this is a main frame navigation, we create a new RWHV (and a
//    `gfx::NativeView`) for the speculative RFH. Now OOPIF and the speculative
//    RFH share the same RVH and RWHV. 2 and 3 happen before the browser hear
//    back from the server with the header.
// 4. The server responds with the header "coop=same-site". The browser now
//    realizes the BI needs to be swapped. The old page and the OOPIF are
//    BFCached, but the OOPIF still has reference to a RWHV/NativeView that it
//    shouldn't have.
//
// TODO(crbug.com/40285569):
// - A page shouldn't be BFCached if it is no longer reachable via session
//   history navigations (i.e., if the navigation entry is replaced).
// - When the browser is in a steady state with no on-going navigations, there
//   should only be one `RenderWidgetHostView` for the main frame, one only one
//   `gfx::NativeView` under the WebContents.
class RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest
    : public RenderWidgetHostViewBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest() =
      default;
  ~RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest()
      override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // For OOPIF. a.test, b.test etc will be in their respective
    // `SiteInstanceGroup`s.
    command_line->AppendSwitch(switches::kSitePerProcess);

    std::vector<base::test::FeatureRefAndParams> enabled_features;
    bool bfcache_enabled = GetParam();
    if (bfcache_enabled) {
      scoped_feature_list_.InitWithFeaturesAndParameters(
          GetDefaultEnabledBackForwardCacheFeaturesForTesting(enabled_features),
          GetDefaultDisabledBackForwardCacheFeaturesForTesting());
    } else {
      scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
      command_line->AppendSwitch(switches::kDisableBackForwardCache);
    }
    // Disable the delay of creating the speculative RFH for test
    // TouchEventsForwardedToTheCorrectRenderWidgetHostView.
    // The test involves receiving a coop header for a non-coop speculative RFH.
    // The speculatve RFH must be created when the request is sent.
    feature_list_for_defer_speculative_rfh_.InitAndEnableFeatureWithParameters(
        features::kDeferSpeculativeRFHCreation,
        {{"create_speculative_rfh_delay_ms", "0"}});

    RenderWidgetHostViewBrowserTest::SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    ASSERT_TRUE(AreAllSitesIsolatedForTesting());

    host_resolver()->AddRule("*", "127.0.0.1");

    https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    net::test_server::RegisterDefaultHandlers(https_server());

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

  bool SetUpSourceSurface(const char* wait_message) override { return false; }

  RenderFrameHostImpl* AddSubframe(WebContentsImpl* web_contents,
                                   const GURL& url) {
    auto* main_frame = web_contents->GetPrimaryMainFrame();

    static constexpr char kAddIFrame[] = R"({
        const iframe = document.createElement('iframe');
        iframe.src = $1;
        document.body.appendChild(iframe);
      })";
    TestNavigationObserver observer(web_contents);
    EXPECT_TRUE(ExecJs(main_frame, JsReplace(kAddIFrame, url)));
    observer.Wait();
    EXPECT_EQ(main_frame->frame_tree_node()->child_count(), 1U);
    return main_frame->frame_tree_node()->child_at(0u)->current_frame_host();
  }

  void NavigateMainFrameFromSubframeAndWait(RenderFrameHost* subframe_rfh,
                                            const GURL& url) {
    TestNavigationObserver observer(web_contents());
    ASSERT_TRUE(ExecJs(subframe_rfh,
                       JsReplace("window.top.location.replace($1)", url)));
    observer.Wait();
  }

  net::EmbeddedTestServer* https_server() { return &https_server_; }

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

 private:
  net::EmbeddedTestServer https_server_{
      net::EmbeddedTestServer::Type::TYPE_HTTPS};
  base::test::ScopedFeatureList scoped_feature_list_;
  base::test::ScopedFeatureList feature_list_for_defer_speculative_rfh_;
};

std::string DescribeBFCacheFeatureStatus(
    const ::testing::TestParamInfo<bool>& info) {
  if (info.param) {
    return "BFCache_Enabled";
  } else {
    return "BFCache_Disabled";
  }
}

}  // namespace

// TODO(crbug.com/40285569): When fix the BFCache behavior, move this
// test into "back_forward_cache_basics_browsertest.cc". Temporarily placed here
// to reuse the testing harness.
IN_PROC_BROWSER_TEST_P(
    RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest,
    NonHistoryTraversablePageShouldNotBeBFCached) {
  ASSERT_TRUE(NavigateToURL(web_contents(),
                            https_server()->GetURL("a.test", "/title1.html")));

  RenderFrameHostWrapper subframe_rfh(AddSubframe(
      web_contents(), https_server()->GetURL("b.test", "/title2.html")));
  RenderFrameHostWrapper old_main_frame(web_contents()->GetPrimaryMainFrame());

  NavigateMainFrameFromSubframeAndWait(
      subframe_rfh.get(),
      https_server()->GetURL(
          "b.test", "/set-header?Cross-Origin-Opener-Policy: same-origin"));

  // Location.replace navigaion replaces the navigation entry of the old page.
  // We can't go back.
  ASSERT_EQ(web_contents()->GetPrimaryFrameTree().controller().GetEntryCount(),
            1);

  bool bfcache_enabled = GetParam();
  if (bfcache_enabled) {
    // TODO(crbug.com/40285569): We shouldn't store the old page and its
    // OOPIF in the BFCache.
    ASSERT_FALSE(old_main_frame.IsDestroyed());
    ASSERT_FALSE(subframe_rfh.IsDestroyed());
    ASSERT_TRUE(static_cast<RenderFrameHostImpl*>(old_main_frame.get())
                    ->IsInBackForwardCache());
    ASSERT_TRUE(static_cast<RenderFrameHostImpl*>(subframe_rfh.get())
                    ->IsInBackForwardCache());
  } else {
    ASSERT_TRUE(old_main_frame.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(subframe_rfh.WaitUntilRenderFrameDeleted());
  }
}

// Regression test for b/302490197: the touch events should always be forwarded
// to the main frame's `RenderWidgetHostViewAndroid` and its `ui::ViewAndroid`,
// no matter if there are redundant RWHVAs / VAs under the same WebContents.
#if BUILDFLAG(IS_ANDROID)

IN_PROC_BROWSER_TEST_P(
    RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest,
    TouchEventsForwardedToTheCorrectRenderWidgetHostView) {
  ASSERT_TRUE(NavigateToURL(web_contents(),
                            https_server()->GetURL("a.test", "/title1.html")));

  RenderFrameHostWrapper subframe_rfh(AddSubframe(
      web_contents(), https_server()->GetURL("b.test", "/title2.html")));
  RenderFrameHostWrapper old_main_frame(web_contents()->GetPrimaryMainFrame());

  NavigateMainFrameFromSubframeAndWait(
      subframe_rfh.get(),
      https_server()->GetURL(
          "b.test", "/set-header?Cross-Origin-Opener-Policy: same-origin"));

  bool bfcache_enabled = GetParam();
  if (!bfcache_enabled) {
    ASSERT_TRUE(old_main_frame.WaitUntilRenderFrameDeleted());
    ASSERT_TRUE(subframe_rfh.WaitUntilRenderFrameDeleted());
  }

  // Three RWHV when BFCache is enabled: old main frame and its OOPIF, and the
  // new main frame.
  //
  // TODO(crbug.com/40285569): The number of RWHVs should be one,
  // regardless of BFCache.
  size_t num_expected_rwhv = bfcache_enabled ? 3u : 1u;
  size_t num_actual_rwhv = 0u;
  static_cast<WebContents*>(web_contents())
      ->ForEachRenderFrameHost([&num_actual_rwhv](RenderFrameHost* rfh) {
        if (rfh->GetView()) {
          ++num_actual_rwhv;
        }
      });
  ASSERT_EQ(num_actual_rwhv, num_expected_rwhv);

  // On Android, when the old main frame is unloaded, we explicitly call
  // `RWHVA::UpdateNativeViewTree()` to remove the old main frame's native
  // view from the native view tree. Thus the number of ViewAndroids is two
  // instead of three, when the old main frame and the OOPIF are BFCached. See
  // `WebContentsViewAndroid::RenderViewHostChanged()`.
  // If the DeferSpeculativeRFHCreation feature is enabled, the RWHV won't be
  // created when the navigation starts so only one native view will be left.
  // For some reason the android view for the first speculative RFH is not
  // removed when the response arrives (a new speculiatve RFH will be created).
  //
  // TODO(crbug.com/40285569): The number of `ui::ViewAndroid`s should be
  // one, regardless of BFCache.
  size_t num_expected_native_view = bfcache_enabled ? 2u : 1u;
  auto* web_contents_view_android =
      static_cast<ui::ViewAndroid*>(web_contents()->GetNativeView());

  ASSERT_EQ(web_contents_view_android->GetChildrenCountForTesting(),
            num_expected_native_view);
  // b/302490197: The top-most child `gfx::NativeView` under the WebContents
  // should be the one of the primary main frame, regardless if any other
  // siblings exist. This native view of the primary main frame is responsible
  // for receiving gesture events, thus has to be the top-most.
  ASSERT_EQ(web_contents_view_android->GetTopMostChildForTesting(),
            web_contents()->GetPrimaryMainFrame()->GetNativeView());
}
#endif  // BUILDFLAG(IS_ANDROID)

INSTANTIATE_TEST_SUITE_P(
    All,
    RenderWidgetHostViewOOPIFNavigatesMainFrameLocationReplaceBrowserTest,
    testing::Bool(),
    &DescribeBFCacheFeatureStatus);

}  // namespace content
