chromium/content/browser/renderer_host/render_widget_host_view_browsertest.cc

// 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 <memory>
#include <string>
#include <utility>

#include <stdint.h>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.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 "build/chromeos_buildflags.h"
#include "cc/slim/layer_tree.h"
#include "cc/slim/surface_layer.h"
#include "components/viz/common/features.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/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/mojom/page/page_visibility_state.mojom-shared.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/display/display_switches.h"
#include "ui/gfx/geometry/size_conversions.h"

#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 "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.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)

}  // 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 {};

// 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 {};

class RenderWidgetHostViewBrowserTestBase : public ContentBrowserTest {};

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

// Ensures that kBackForwardCache is always enabled to ensure that a new RWH is
// created on navigation.
class PaintHoldingRenderWidgetHostViewBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {};

// 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) {}

// 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) {}

// 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) {}

// 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) {}

#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) {}
#endif  // BUILDFLAG(IS_ANDROID)

viz::SurfaceId GetCurrentSurfaceIdOnDelegatedFrameHost(
    RenderWidgetHostView* view) {}

viz::SurfaceId GetPreNavigationSurfaceIdOnDelegatedFrameHost(
    RenderWidgetHostView* view) {}

viz::SurfaceId GetFallbackSurfaceId(RenderWidgetHostView* view) {}

class BFCachedRenderWidgetHostViewBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {};
}  // namespace

IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCacheRestoredPageHasNewLocalSurfaceId) {}

// 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,
    BFCachedPageResizedWhileHiddenShouldNotHavePreservedFallback) {}

// Same as above, except that the resize operation is a no-op.
IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCachedPageNoopResizedWhileHiddenHasPreservedFallback) {}

IN_PROC_BROWSER_TEST_F(BFCachedRenderWidgetHostViewBrowserTest,
                       BFCachedViewShouldNotBeEvicted) {}

// 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) {}

namespace {

std::unique_ptr<net::test_server::HttpResponse> HandleSlowStyleSheet(
    const net::test_server::HttpRequest& request) {}

class DOMContentLoadedObserver : public WebContentsObserver {};

}  // namespace

IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       ColorSchemeMetaBackground) {}

IN_PROC_BROWSER_TEST_F(NoCompositingRenderWidgetHostViewBrowserTest,
                       NoColorSchemeMetaBackground) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewBrowserTestBase,
                       CompositorWorksWhenReusingRenderer) {}

enum CompositingMode {};

class CompositingRenderWidgetHostViewBrowserTest
    : public RenderWidgetHostViewBrowserTest,
      public testing::WithParamInterface<CompositingMode> {};

// 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) {}

// 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) {}

class CompositingRenderWidgetHostViewBrowserTestTabCapture
    : public CompositingRenderWidgetHostViewBrowserTest {};

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Origin_Unscaled) {}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Origin_Scaled) {}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Cropped_Unscaled) {}

IN_PROC_BROWSER_TEST_P(CompositingRenderWidgetHostViewBrowserTestTabCapture,
                       CopyFromSurface_Cropped_Scaled) {}

class CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI
    : public CompositingRenderWidgetHostViewBrowserTestTabCapture {};

// 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
#define MAYBE_CopyToBitmap_CenterRegion
#define MAYBE_CopyToBitmap_ScaledResult
#else
#define MAYBE_CopyToBitmap_EntireRegion
#define MAYBE_CopyToBitmap_CenterRegion
#define MAYBE_CopyToBitmap_ScaledResult
#endif

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_EntireRegion) {}

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_CenterRegion) {}

IN_PROC_BROWSER_TEST_P(
    CompositingRenderWidgetHostViewBrowserTestTabCaptureHighDPI,
    MAYBE_CopyToBitmap_ScaledResult) {}

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

INSTANTIATE_TEST_SUITE_P();
INSTANTIATE_TEST_SUITE_P();
INSTANTIATE_TEST_SUITE_P();

class RenderWidgetHostViewPresentationFeedbackBrowserTest
    : public NoCompositingRenderWidgetHostViewBrowserTest {};

// TODO(crbug.com/353234554): Flaky on linux-lacros-tester-rel.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_Show
#else
#define MAYBE_Show
#endif
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       MAYBE_Show) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowThenHide) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       HiddenButPainting) {}

// TODO(crbug.com/353234554): Flaky on linux-lacros-tester-rel.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_ShowWhileCapturing
#else
#define MAYBE_ShowWhileCapturing
#endif
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       MAYBE_ShowWhileCapturing) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       HideWhileCapturing) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowWithoutTabSwitchRequest) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewPresentationFeedbackBrowserTest,
                       ShowThenHideWithoutTabSwitchRequest) {}

#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 SkBitmap& btimap) {
  ASSERT_FALSE(!compositor->GetLayerTreeForTesting()
                    ->GetSurfaceRangesForTesting()
                    .contains(range));
  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::BindOnce(&CheckSurfaceRangeRemovedAfterCopy, range_for_copy,
                     compositor, run_loop.QuitClosure()));
  EXPECT_THAT(
      compositor->GetLayerTreeForTesting()->GetSurfaceRangesForTesting(),
      testing::UnorderedElementsAre(std::make_pair(range_for_copy, 1),
                                    std::make_pair(range_for_mainframe, 1)));
  run_loop.Run(FROM_HERE);
}

namespace {

void AssertSnapshotIsPureWhite(base::RepeatingClosure resume_test,
                               const SkBitmap& snapshot) {
  for (int r = 0; r < snapshot.height(); ++r) {
    for (int c = 0; c < snapshot.width(); ++c) {
      ASSERT_EQ(snapshot.getColor(c, r), SK_ColorWHITE);
    }
  }
  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<RenderWidgetHostViewBase*>(old_view)
              ->CopyFromExactSurface(gfx::Rect(), gfx::Size(),
                                     base::BindOnce(&AssertSnapshotIsPureWhite,
                                                    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> {};

std::string DescribeBFCacheFeatureStatus(
    const ::testing::TestParamInfo<bool>& info) {}

}  // 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) {}

// 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();

}  // namespace content