blob: e07dfa9920f72cf8f149d6d8e2b4937c679e08c8 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/input/cursor_manager.h"
#include <memory>
#include <utility>
#include "build/build_config.h"
#include "content/browser/renderer_host/mock_render_widget_host.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/site_instance_group.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/mock_render_widget_host_delegate.h"
#include "content/test/test_render_view_host.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/gfx/geometry/point.h"
// CursorManager is only instantiated on Aura and Mac.
#if defined(USE_AURA) || BUILDFLAG(IS_MAC)
namespace content {
namespace {
const ui::Cursor kCursorHand(ui::mojom::CursorType::kHand);
const ui::Cursor kCursorCross(ui::mojom::CursorType::kCross);
const ui::Cursor kCursorPointer(ui::mojom::CursorType::kPointer);
ui::Cursor CreateCustomCursor(int width, int height) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
bitmap.eraseColor(SK_ColorRED);
return ui::Cursor::NewCustom(std::move(bitmap), /*hotspot=*/gfx::Point(0, 0),
/*image_scale_factor=*/1.0f);
}
const ui::Cursor kCursorCustom = CreateCustomCursor(0, 0);
class MockRenderWidgetHostViewForCursors : public TestRenderWidgetHostView {
public:
MockRenderWidgetHostViewForCursors(RenderWidgetHost* host, bool top_view)
: TestRenderWidgetHostView(host) {
if (top_view) {
cursor_manager_ = std::make_unique<input::CursorManager>(this);
}
}
void DisplayCursor(const ui::Cursor& cursor) override {
current_cursor_ = cursor;
}
input::CursorManager* GetCursorManager() override {
return cursor_manager_.get();
}
const ui::Cursor& cursor() { return current_cursor_; }
private:
ui::Cursor current_cursor_;
std::unique_ptr<input::CursorManager> cursor_manager_;
};
class CursorManagerTest : public testing::Test {
public:
CursorManagerTest() = default;
CursorManagerTest(const CursorManagerTest&) = delete;
CursorManagerTest& operator=(const CursorManagerTest&) = delete;
void SetUp() override {
browser_context_ = std::make_unique<TestBrowserContext>();
process_host_ =
std::make_unique<MockRenderProcessHost>(browser_context_.get());
site_instance_group_ =
base::WrapRefCounted(SiteInstanceGroup::CreateForTesting(
browser_context_.get(), process_host_.get()));
widget_host_ = MakeNewWidgetHost();
top_view_ = std::make_unique<MockRenderWidgetHostViewForCursors>(
widget_host_.get(), true);
}
std::unique_ptr<RenderWidgetHostImpl> MakeNewWidgetHost() {
int32_t routing_id = process_host_->GetNextRoutingID();
return MockRenderWidgetHost::Create(
/*frame_tree=*/nullptr, &delegate_, site_instance_group_->GetSafeRef(),
routing_id);
}
void TearDown() override {
top_view_.reset();
widget_host_ = nullptr;
process_host_->Cleanup();
site_instance_group_.reset();
process_host_ = nullptr;
}
protected:
BrowserTaskEnvironment task_environment_;
std::unique_ptr<BrowserContext> browser_context_;
std::unique_ptr<MockRenderProcessHost> process_host_;
scoped_refptr<SiteInstanceGroup> site_instance_group_;
std::unique_ptr<RenderWidgetHostImpl> widget_host_;
std::unique_ptr<MockRenderWidgetHostViewForCursors> top_view_;
MockRenderWidgetHostDelegate delegate_;
};
} // namespace
// Verify basic CursorManager functionality when no OOPIFs are present.
TEST_F(CursorManagerTest, CursorOnSingleView) {
// Simulate mouse over the top-level frame without an UpdateCursor message.
top_view_->GetCursorManager()->UpdateViewUnderCursor(top_view_.get());
// The view should be using the default cursor.
EXPECT_EQ(top_view_->cursor(), ui::Cursor());
// Update the view with a non-default cursor.
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
// Verify the RenderWidgetHostView now uses the correct cursor.
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
// Verify cursor interactions between a parent frame and an out-of-process
// child frame.
TEST_F(CursorManagerTest, CursorOverChildView) {
std::unique_ptr<RenderWidgetHostImpl> widget_host(MakeNewWidgetHost());
std::unique_ptr<MockRenderWidgetHostViewForCursors> child_view(
new MockRenderWidgetHostViewForCursors(widget_host.get(), false));
// Set the child frame's cursor to a hand. This should not propagate to the
// top-level view without the mouse moving over the child frame.
top_view_->GetCursorManager()->UpdateCursor(child_view.get(), kCursorHand);
EXPECT_NE(top_view_->cursor(), kCursorHand);
// Now moving the mouse over the child frame should update the overall cursor.
top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view.get());
EXPECT_EQ(top_view_->cursor(), kCursorHand);
// Destruction of the child view should restore the parent frame's cursor.
top_view_->GetCursorManager()->ViewBeingDestroyed(child_view.get());
EXPECT_NE(top_view_->cursor(), kCursorHand);
}
// Verify interactions between two independent OOPIFs, including interleaving
// cursor updates and mouse movements. This simulates potential race
// conditions between cursor updates.
TEST_F(CursorManagerTest, CursorOverMultipleChildViews) {
std::unique_ptr<RenderWidgetHostImpl> widget_host1(MakeNewWidgetHost());
std::unique_ptr<MockRenderWidgetHostViewForCursors> child_view1(
new MockRenderWidgetHostViewForCursors(widget_host1.get(), false));
std::unique_ptr<RenderWidgetHostImpl> widget_host2(MakeNewWidgetHost());
std::unique_ptr<MockRenderWidgetHostViewForCursors> child_view2(
new MockRenderWidgetHostViewForCursors(widget_host2.get(), false));
// Initialize each View to a different cursor.
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
top_view_->GetCursorManager()->UpdateCursor(child_view1.get(), kCursorCross);
top_view_->GetCursorManager()->UpdateCursor(child_view2.get(),
kCursorPointer);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
// Simulate moving the mouse between child views and receiving cursor updates.
top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view1.get());
EXPECT_EQ(top_view_->cursor(), kCursorCross);
top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view2.get());
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
// Simulate cursor updates to both child views and the parent view. An
// update to child_view1 or the parent view should not change the current
// cursor because the mouse is over child_view2.
top_view_->GetCursorManager()->UpdateCursor(child_view1.get(), kCursorHand);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
top_view_->GetCursorManager()->UpdateCursor(child_view2.get(), kCursorCross);
EXPECT_EQ(top_view_->cursor(), kCursorCross);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
EXPECT_EQ(top_view_->cursor(), kCursorCross);
// Similarly, destroying child_view1 should have no effect on the cursor,
// but destroying child_view2 should change it.
top_view_->GetCursorManager()->ViewBeingDestroyed(child_view1.get());
EXPECT_EQ(top_view_->cursor(), kCursorCross);
top_view_->GetCursorManager()->ViewBeingDestroyed(child_view2.get());
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorsAreNotAllowed) {
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorCustom);
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorsAreNotAllowedAboveSizeLimit) {
const ui::Cursor kCursorCustomLarge = CreateCustomCursor(20, 50);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(),
kCursorCustomLarge);
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/40);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorsAreAllowedBelowSizeLimit) {
const ui::Cursor kCursorCustomLarge = CreateCustomCursor(20, 35);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(),
kCursorCustomLarge);
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/40);
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorSubjectToMultipleSizeLimits) {
const ui::Cursor kCursorCustomLarge = CreateCustomCursor(20, 35);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(),
kCursorCustomLarge);
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
{
auto disallow_scope1 =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/40);
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
auto disallow_scope2 =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/30);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
// Running the first closure leaves the restriction from the second closure
// in place.
disallow_scope1.RunAndReset();
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustomLarge);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_OtherCursorsStillAllowed) {
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorSetDuringScope) {
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorCustom);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorRemovedDuringScope) {
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorCustom);
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
TEST_F(CursorManagerTest, CustomCursorDisallowedScope_MultipleScopes) {
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorCustom);
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
auto disallow_scope1 =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
auto disallow_scope2 =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
disallow_scope1.RunAndReset();
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
auto disallow_scope3 =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
disallow_scope2.RunAndReset();
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
disallow_scope3.RunAndReset();
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
}
TEST_F(CursorManagerTest, CustomCursorDisallowedScope_CustomCursorViewFocused) {
std::unique_ptr<RenderWidgetHostImpl> widget_host(MakeNewWidgetHost());
std::unique_ptr<MockRenderWidgetHostViewForCursors> child_view(
new MockRenderWidgetHostViewForCursors(widget_host.get(), false));
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
top_view_->GetCursorManager()->UpdateCursor(child_view.get(), kCursorCustom);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorHand);
top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view.get());
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
}
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
top_view_->GetCursorManager()->ViewBeingDestroyed(child_view.get());
}
TEST_F(CursorManagerTest,
CustomCursorDisallowedScope_CustomCursorViewFocusRemoved) {
std::unique_ptr<RenderWidgetHostImpl> widget_host(MakeNewWidgetHost());
std::unique_ptr<MockRenderWidgetHostViewForCursors> child_view(
new MockRenderWidgetHostViewForCursors(widget_host.get(), false));
top_view_->GetCursorManager()->UpdateCursor(top_view_.get(), kCursorHand);
top_view_->GetCursorManager()->UpdateCursor(child_view.get(), kCursorCustom);
top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view.get());
EXPECT_EQ(top_view_->cursor(), kCursorCustom);
{
auto disallow_scope =
top_view_->GetCursorManager()->CreateDisallowCustomCursorScope(
/*max_dimension_dips=*/0);
EXPECT_EQ(top_view_->cursor(), kCursorPointer);
top_view_->GetCursorManager()->UpdateViewUnderCursor(top_view_.get());
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
EXPECT_EQ(top_view_->cursor(), kCursorHand);
}
} // namespace content
#endif // defined(USE_AURA) || BUILDFLAG(IS_MAC)