chromium/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc

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

#include "content/browser/renderer_host/render_widget_host_view_aura.h"

#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/run_until.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.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/browser/web_contents_delegate.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/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/screen.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"

#if BUILDFLAG(IS_WIN)
#include "content/browser/renderer_host/legacy_render_widget_host_win.h"
#endif

namespace content {
namespace {

#if BUILDFLAG(IS_CHROMEOS_ASH)
const char kMinimalPageDataURL[] =
    "data:text/html,<html><head></head><body>Hello, world</body></html>";

// Run the current message loop for a short time without unwinding the current
// call stack.
void GiveItSomeTime() {
  base::RunLoop run_loop;
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(250));
  run_loop.Run();
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

class FakeWebContentsDelegate : public WebContentsDelegate {};

}  // namespace

class RenderWidgetHostViewAuraBrowserTest : public ContentBrowserTest {};

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest, AuraWindowLookup) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
  aura::Window* window = GetRenderWidgetHostView()->GetNativeView();
  ASSERT_TRUE(GetLegacyRenderWidgetHostHWND());
  HWND hwnd = GetLegacyRenderWidgetHostHWND()->hwnd();
  EXPECT_TRUE(hwnd);
  auto* window_tree_host = aura::WindowTreeHost::GetForAcceleratedWidget(hwnd);
  EXPECT_TRUE(window_tree_host);
  EXPECT_EQ(window->GetHost(), window_tree_host);
}
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_CHROMEOS_ASH)
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       // TODO(crbug.com/40874148): Re-enable this test
                       // TODO(crbug.com/40873813): Re-enable this test
                       DISABLED_StaleFrameContentOnEvictionNormal) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));

  // Make sure the renderer submits at least one frame before hiding it.
  RenderFrameSubmissionObserver submission_observer(shell()->web_contents());
  if (!submission_observer.render_frame_count())
    submission_observer.WaitForAnyFrameSubmission();

  FakeWebContentsDelegate delegate;
  delegate.SetShowStaleContentOnEviction(true);
  shell()->web_contents()->SetDelegate(&delegate);

  // Initially there should be no stale content set.
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Hide the view and evict the frame. This should trigger a copy of the stale
  // frame content.
  GetRenderWidgetHostView()->Hide();
  auto* dfh = GetDelegatedFrameHost();
  static_cast<viz::FrameEvictorClient*>(dfh)->EvictDelegatedFrame(
      dfh->GetFrameEvictorForTesting()->CollectSurfaceIdsForEviction());
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kPendingEvictionRequests);

  // Wait until the stale frame content is copied and set onto the layer.
  while (!GetDelegatedFrameHost()->stale_content_layer_->has_external_content())
    GiveItSomeTime();

  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Unhidding the view should reset the stale content layer to show the new
  // frame content.
  GetRenderWidgetHostView()->Show();
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       StaleFrameContentOnEvictionRejected) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));

  // Wait for first frame activation when a surface is embedded.
  while (!GetDelegatedFrameHost()->HasSavedFrame())
    GiveItSomeTime();

  FakeWebContentsDelegate delegate;
  delegate.SetShowStaleContentOnEviction(true);
  shell()->web_contents()->SetDelegate(&delegate);

  // Initially there should be no stale content set.
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Hide the view and evict the frame. This should trigger a copy of the stale
  // frame content.
  GetRenderWidgetHostView()->Hide();
  auto* dfh = GetDelegatedFrameHost();
  static_cast<viz::FrameEvictorClient*>(dfh)->EvictDelegatedFrame(
      dfh->GetFrameEvictorForTesting()->CollectSurfaceIdsForEviction());
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kPendingEvictionRequests);

  GetRenderWidgetHostView()->Show();
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Wait until the stale frame content is copied and the result callback is
  // complete.
  GiveItSomeTime();

  // This should however not set the stale content as the view is visible and
  // new frames are being submitted.
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       StaleFrameContentOnEvictionNone) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL(kMinimalPageDataURL)));

  // Wait for first frame activation when a surface is embedded.
  while (!GetDelegatedFrameHost()->HasSavedFrame())
    GiveItSomeTime();

  FakeWebContentsDelegate delegate;
  delegate.SetShowStaleContentOnEviction(false);
  shell()->web_contents()->SetDelegate(&delegate);

  // Initially there should be no stale content set.
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Hide the view and evict the frame. This should not trigger a copy of the
  // stale frame content as the WebContentDelegate returns false.
  GetRenderWidgetHostView()->Hide();
  auto* dfh = GetDelegatedFrameHost();
  static_cast<viz::FrameEvictorClient*>(dfh)->EvictDelegatedFrame(
      dfh->GetFrameEvictorForTesting()->CollectSurfaceIdsForEviction());

  EXPECT_EQ(GetDelegatedFrameHost()->frame_eviction_state_,
            DelegatedFrameHost::FrameEvictionState::kNotStarted);

  // Wait for a while to ensure any copy requests that were sent out are not
  // completed. There shouldnt be any requests sent however.
  GiveItSomeTime();
  EXPECT_FALSE(
      GetDelegatedFrameHost()->stale_content_layer_->has_external_content());
}
#endif  // #if BUILDFLAG(IS_CHROMEOS_ASH)

// TODO(crbug.com/40148102): fix the way how exo creates accelerated widgets. At
// the moment, they are created only after the client attaches a buffer to a
// surface, which is incorrect and results in the "[destroyed object]: error 1:
// popup parent not constructed" error.
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#define MAYBE_SetKeyboardFocusOnTapAfterDismissingPopup
#else
#define MAYBE_SetKeyboardFocusOnTapAfterDismissingPopup
#endif
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       MAYBE_SetKeyboardFocusOnTapAfterDismissingPopup) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       UpdatesCaretBoundsAfterFrameScroll) {}

IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
                       UpdatesCaretBoundsAfterOverflowScroll) {}

class RenderWidgetHostViewAuraDevtoolsBrowserTest
    : public content::DevToolsProtocolTest {};

// This test opens a select popup which inside breaks the debugger
// which enters a nested event loop.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraDevtoolsBrowserTest,
                       NoCrashOnSelect) {}

// Used to verify features under the environment whose device scale factor is 2.
class RenderWidgetHostViewAuraDSFBrowserTest
    : public RenderWidgetHostViewAuraBrowserTest {};

// Verifies the bounding box of the selection region.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraDSFBrowserTest,
                       SelectionRegionBoundingBox) {}

class RenderWidgetHostViewAuraActiveWidgetTest : public ContentBrowserTest {};

// In this test, toggling the value of 'active' state changes the
// active state of frame on the renderer side. Cross origin iframes
// are checked to ensure the active state is replicated across all
// processes. SimulateActiveStateForWidget toggles the 'active' state
// of widget over IPC.
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraActiveWidgetTest,
                       FocusIsInactive) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
// Verifies that getting active input control accounts for iframe positioning.
// Flaky: crbug.com/1293700
IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraActiveWidgetTest,
                       DISABLED_TextControlBoundingRegionInIframe) {
  GURL page(
      embedded_test_server()->GetURL("example.com", "/input_in_iframe.html"));
  EXPECT_TRUE(NavigateToURL(shell(), page));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetPrimaryFrameTree()
                            .root();

  // Ensure both the main page and the iframe are loaded.
  ASSERT_EQ("OUTER_LOADED",
            EvalJs(root->current_frame_host(), "notifyWhenLoaded()"));
  ASSERT_EQ("LOADED", EvalJs(root->current_frame_host(),
                             "document.querySelector(\"iframe\").contentWindow."
                             "notifyWhenLoaded();"));
  // TODO(b/204006085): Remove this sleep call and replace with polling.
  GiveItSomeTime();

  std::optional<gfx::Rect> control_bounds;
  std::optional<gfx::Rect> selection_bounds;
  GetRenderWidgetHostView()->GetActiveTextInputControlLayoutBounds(
      &control_bounds, &selection_bounds);

  // 4000px from input offset inside input_box.html
  // 200px from input_in_iframe.html
  EXPECT_TRUE(control_bounds.has_value());
  ASSERT_EQ(4200, control_bounds->origin().y());
}
#endif

}  // namespace content