|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // 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/cursor_manager.h" | 
|  | #include <memory> | 
|  |  | 
|  | #include "build/build_config.h" | 
|  | #include "content/browser/renderer_host/agent_scheduling_group_host.h" | 
|  | #include "content/browser/renderer_host/mock_render_widget_host.h" | 
|  | #include "content/browser/renderer_host/render_widget_host_impl.h" | 
|  | #include "content/common/cursors/webcursor.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 "ui/base/cursor/mojom/cursor_type.mojom-shared.h" | 
|  |  | 
|  | // CursorManager is only instantiated on Aura and Mac. | 
|  | #if defined(USE_AURA) || defined(OS_MAC) | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class MockRenderWidgetHostViewForCursors : public TestRenderWidgetHostView { | 
|  | public: | 
|  | MockRenderWidgetHostViewForCursors(RenderWidgetHost* host, bool top_view) | 
|  | : TestRenderWidgetHostView(host) { | 
|  | if (top_view) | 
|  | cursor_manager_ = std::make_unique<CursorManager>(this); | 
|  | } | 
|  |  | 
|  | void DisplayCursor(const WebCursor& cursor) override { | 
|  | current_cursor_ = cursor; | 
|  | } | 
|  |  | 
|  | CursorManager* GetCursorManager() override { return cursor_manager_.get(); } | 
|  |  | 
|  | const WebCursor& cursor() { return current_cursor_; } | 
|  |  | 
|  | private: | 
|  | WebCursor current_cursor_; | 
|  | std::unique_ptr<CursorManager> cursor_manager_; | 
|  | }; | 
|  |  | 
|  | class CursorManagerTest : public testing::Test { | 
|  | public: | 
|  | CursorManagerTest() = default; | 
|  |  | 
|  | void SetUp() override { | 
|  | browser_context_ = std::make_unique<TestBrowserContext>(); | 
|  | process_host_ = | 
|  | std::make_unique<MockRenderProcessHost>(browser_context_.get()); | 
|  | agent_scheduling_group_host_ = | 
|  | std::make_unique<AgentSchedulingGroupHost>(*process_host_); | 
|  | widget_host_ = MakeNewWidgetHost(); | 
|  | top_view_ = | 
|  | new MockRenderWidgetHostViewForCursors(widget_host_.get(), true); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<RenderWidgetHostImpl> MakeNewWidgetHost() { | 
|  | int32_t routing_id = process_host_->GetNextRoutingID(); | 
|  | return MockRenderWidgetHost::Create( | 
|  | /*frame_tree=*/nullptr, &delegate_, *agent_scheduling_group_host_, | 
|  | routing_id); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | if (top_view_) | 
|  | delete top_view_; | 
|  |  | 
|  | widget_host_ = nullptr; | 
|  | process_host_->Cleanup(); | 
|  | agent_scheduling_group_host_ = nullptr; | 
|  | process_host_ = nullptr; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | BrowserTaskEnvironment task_environment_; | 
|  |  | 
|  | std::unique_ptr<BrowserContext> browser_context_; | 
|  | std::unique_ptr<MockRenderProcessHost> process_host_; | 
|  | std::unique_ptr<AgentSchedulingGroupHost> agent_scheduling_group_host_; | 
|  | std::unique_ptr<RenderWidgetHostImpl> widget_host_; | 
|  |  | 
|  | // Tests should set this to nullptr if they've already triggered its | 
|  | // destruction. | 
|  | MockRenderWidgetHostViewForCursors* top_view_; | 
|  |  | 
|  | MockRenderWidgetHostDelegate delegate_; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(CursorManagerTest); | 
|  | }; | 
|  |  | 
|  | }  // 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_); | 
|  |  | 
|  | // The view should be using the default cursor. | 
|  | EXPECT_EQ(top_view_->cursor(), WebCursor()); | 
|  |  | 
|  | WebCursor cursor_hand(ui::mojom::CursorType::kHand); | 
|  |  | 
|  | // Update the view with a non-default cursor. | 
|  | top_view_->GetCursorManager()->UpdateCursor(top_view_, cursor_hand); | 
|  |  | 
|  | // Verify the RenderWidgetHostView now uses the correct cursor. | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_hand); | 
|  | } | 
|  |  | 
|  | // 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)); | 
|  |  | 
|  | WebCursor cursor_hand(ui::mojom::CursorType::kHand); | 
|  |  | 
|  | // 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(), cursor_hand); | 
|  | EXPECT_NE(top_view_->cursor(), cursor_hand); | 
|  |  | 
|  | // 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(), cursor_hand); | 
|  |  | 
|  | // Destruction of the child view should restore the parent frame's cursor. | 
|  | top_view_->GetCursorManager()->ViewBeingDestroyed(child_view.get()); | 
|  | EXPECT_NE(top_view_->cursor(), cursor_hand); | 
|  | } | 
|  |  | 
|  | // 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)); | 
|  |  | 
|  | WebCursor cursor_hand(ui::mojom::CursorType::kHand); | 
|  |  | 
|  | WebCursor cursor_cross(ui::mojom::CursorType::kCross); | 
|  |  | 
|  | WebCursor cursor_pointer(ui::mojom::CursorType::kPointer); | 
|  |  | 
|  | // Initialize each View to a different cursor. | 
|  | top_view_->GetCursorManager()->UpdateCursor(top_view_, cursor_hand); | 
|  | top_view_->GetCursorManager()->UpdateCursor(child_view1.get(), cursor_cross); | 
|  | top_view_->GetCursorManager()->UpdateCursor(child_view2.get(), | 
|  | cursor_pointer); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_hand); | 
|  |  | 
|  | // Simulate moving the mouse between child views and receiving cursor updates. | 
|  | top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view1.get()); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_cross); | 
|  | top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view2.get()); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_pointer); | 
|  |  | 
|  | // 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(), cursor_hand); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_pointer); | 
|  | top_view_->GetCursorManager()->UpdateCursor(child_view2.get(), cursor_cross); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_cross); | 
|  | top_view_->GetCursorManager()->UpdateCursor(top_view_, cursor_hand); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_cross); | 
|  |  | 
|  | // 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(), cursor_cross); | 
|  | top_view_->GetCursorManager()->ViewBeingDestroyed(child_view2.get()); | 
|  | EXPECT_EQ(top_view_->cursor(), cursor_hand); | 
|  | } | 
|  |  | 
|  | }  // namespace content | 
|  |  | 
|  | #endif  // defined(USE_AURA) || defined(OS_MAC) |