| // 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_.reset(MakeNewWidgetHost()); | 
 |     top_view_ = | 
 |         new MockRenderWidgetHostViewForCursors(widget_host_.get(), true); | 
 |   } | 
 |  | 
 |   RenderWidgetHostImpl* MakeNewWidgetHost() { | 
 |     int32_t routing_id = process_host_->GetNextRoutingID(); | 
 |     return MockRenderWidgetHost::Create( | 
 |         &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) |