Coordinate cursor updates from multiple renderers on a single page
Currently each RenderWidget sends cursor update messages independently
based on its last seen mouse position, with no awareness of other
RenderWidgets overriding the mouse cursor. This causes problem due to
race conditions where RenderWidgets in multiple renderer processes are
changing the cursor graphic as the user moves the mouse, and also
creates inconsistency because a RenderWidget does not know that a
different RenderWidget has modified the cursor.
This CL adds a CursorManager class to content, which tracks the last
cursor update received by each RenderWidgetHostView, and signals the
root RWHV to show the correct cursor for the view that the mouse is
currently over.
Bug: 614540, 545237
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_site_isolation
Change-Id: Id7355b69f13f9e13a5113b5f63228ddf067352ed
Reviewed-on: https://chromium-review.googlesource.com/558271
Commit-Queue: Ken Buchanan <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/master@{#486528}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index e069154..7420c0ca 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1111,6 +1111,8 @@
"renderer_host/clipboard_message_filter.cc",
"renderer_host/clipboard_message_filter.h",
"renderer_host/clipboard_message_filter_mac.mm",
+ "renderer_host/cursor_manager.cc",
+ "renderer_host/cursor_manager.h",
"renderer_host/database_message_filter.cc",
"renderer_host/database_message_filter.h",
"renderer_host/dip_util.cc",
diff --git a/content/browser/frame_host/cross_process_frame_connector.cc b/content/browser/frame_host/cross_process_frame_connector.cc
index 6888b92f..261efa0 100644
--- a/content/browser/frame_host/cross_process_frame_connector.cc
+++ b/content/browser/frame_host/cross_process_frame_connector.cc
@@ -13,6 +13,7 @@
#include "content/browser/frame_host/render_frame_host_manager.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
+#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
@@ -57,6 +58,10 @@
RenderWidgetHostViewChildFrame* view) {
// Detach ourselves from the previous |view_|.
if (view_) {
+ RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
+ if (root_view && root_view->GetCursorManager())
+ root_view->GetCursorManager()->ViewBeingDestroyed(view_);
+
// The RenderWidgetHostDelegate needs to be checked because set_view() can
// be called during nested WebContents destruction. See
// https://crbug.com/644306.
@@ -112,8 +117,10 @@
void CrossProcessFrameConnector::UpdateCursor(const WebCursor& cursor) {
RenderWidgetHostViewBase* root_view = GetRootRenderWidgetHostView();
- if (root_view)
- root_view->UpdateCursor(cursor);
+ // UpdateCursor messages are ignored if the root view does not support
+ // cursors.
+ if (root_view && root_view->GetCursorManager())
+ root_view->GetCursorManager()->UpdateCursor(view_, cursor);
}
gfx::Point CrossProcessFrameConnector::TransformPointToRootCoordSpace(
diff --git a/content/browser/renderer_host/cursor_manager.cc b/content/browser/renderer_host/cursor_manager.cc
new file mode 100644
index 0000000..e32ad8e
--- /dev/null
+++ b/content/browser/renderer_host/cursor_manager.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2017 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 "cursor_manager.h"
+
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+
+namespace content {
+
+CursorManager::CursorManager(RenderWidgetHostViewBase* root)
+ : view_under_cursor_(root), root_view_(root) {}
+
+CursorManager::~CursorManager() {}
+
+void CursorManager::UpdateCursor(RenderWidgetHostViewBase* view,
+ const WebCursor& cursor) {
+ cursor_map_[view] = cursor;
+ if (view == view_under_cursor_)
+ root_view_->DisplayCursor(cursor);
+}
+
+void CursorManager::UpdateViewUnderCursor(RenderWidgetHostViewBase* view) {
+ view_under_cursor_ = view;
+ WebCursor cursor;
+
+ // If no UpdateCursor has been received for this view, use an empty cursor.
+ auto it = cursor_map_.find(view);
+ if (it != cursor_map_.end())
+ cursor = it->second;
+
+ root_view_->DisplayCursor(cursor);
+}
+
+void CursorManager::ViewBeingDestroyed(RenderWidgetHostViewBase* view) {
+ cursor_map_.erase(view);
+
+ // If the view right under the mouse is going away, use the root's cursor
+ // until UpdateViewUnderCursor is called again.
+ if (view == view_under_cursor_ && view != root_view_)
+ UpdateViewUnderCursor(root_view_);
+}
+
+bool CursorManager::GetCursorForTesting(RenderWidgetHostViewBase* view,
+ WebCursor& cursor) {
+ if (cursor_map_.find(view) == cursor_map_.end())
+ return false;
+
+ cursor = cursor_map_[view];
+ return true;
+}
+
+} // namespace content
\ No newline at end of file
diff --git a/content/browser/renderer_host/cursor_manager.h b/content/browser/renderer_host/cursor_manager.h
new file mode 100644
index 0000000..d1480279
--- /dev/null
+++ b/content/browser/renderer_host/cursor_manager.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2017 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_CURSOR_MANAGER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_CURSOR_MANAGER_H_
+
+#include <map>
+
+#include "content/common/content_export.h"
+#include "content/common/cursors/webcursor.h"
+
+namespace content {
+
+class RenderWidgetHostViewBase;
+
+// CursorManager coordinates mouse cursors for multiple RenderWidgetHostViews
+// on a single page. It is owned by the top-level RenderWidgetHostView and
+// calls back to its DisplayCursor method when the cursor needs to change,
+// either because the mouse moved over a different view or because a cursor
+// update was received for the current view.
+class CONTENT_EXPORT CursorManager {
+ public:
+ CursorManager(RenderWidgetHostViewBase* root);
+ ~CursorManager();
+
+ // Called for any RenderWidgetHostView that received an UpdateCursor message
+ // from its renderer process.
+ void UpdateCursor(RenderWidgetHostViewBase*, const WebCursor&);
+
+ // Called when the mouse moves over a different RenderWidgetHostView.
+ void UpdateViewUnderCursor(RenderWidgetHostViewBase*);
+
+ // Notification of a RenderWidgetHostView being destroyed, so that its
+ // cursor map entry can be removed if it has one. If it is the current
+ // view_under_cursor_, then the root_view_'s cursor will be displayed.
+ void ViewBeingDestroyed(RenderWidgetHostViewBase*);
+
+ // Accessor for browser tests, enabling verification of the cursor_map_.
+ // Returns false if the provided View is not in the map, and outputs
+ // the cursor otherwise.
+ bool GetCursorForTesting(RenderWidgetHostViewBase*, WebCursor&);
+
+ private:
+ // Stores the last received cursor from each RenderWidgetHostView.
+ std::map<RenderWidgetHostViewBase*, WebCursor> cursor_map_;
+
+ // The view currently underneath the cursor, which corresponds to the cursor
+ // currently displayed.
+ RenderWidgetHostViewBase* view_under_cursor_;
+
+ // The root view is the target for DisplayCursor calls whenever the active
+ // cursor needs to change.
+ RenderWidgetHostViewBase* root_view_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_CURSOR_MANAGER_H_
\ No newline at end of file
diff --git a/content/browser/renderer_host/cursor_manager_unittest.cc b/content/browser/renderer_host/cursor_manager_unittest.cc
new file mode 100644
index 0000000..e522972
--- /dev/null
+++ b/content/browser/renderer_host/cursor_manager_unittest.cc
@@ -0,0 +1,212 @@
+// 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 "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
+#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/common/cursors/webcursor.h"
+#include "content/public/common/cursor_info.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/test/test_render_view_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// CursorManager is only instantiated on Aura and Mac.
+#if defined(USE_AURA) || defined(OS_MACOSX)
+
+namespace content {
+
+namespace {
+
+// TODO(kenrb): This mock is implemented in several unit test files, and
+// could be moved into a common header.
+class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
+ public:
+ MockRenderWidgetHostDelegate() {}
+ ~MockRenderWidgetHostDelegate() override {}
+
+ private:
+ // RenderWidgetHostDelegate:
+ void ExecuteEditCommand(
+ const std::string& command,
+ const base::Optional<base::string16>& value) override {}
+ void Cut() override {}
+ void Copy() override {}
+ void Paste() override {}
+ void SelectAll() override {}
+};
+
+class MockRenderWidgetHostViewForCursors : public TestRenderWidgetHostView {
+ public:
+ MockRenderWidgetHostViewForCursors(RenderWidgetHost* host, bool top_view)
+ : TestRenderWidgetHostView(host) {
+ if (top_view)
+ cursor_manager_.reset(new CursorManager(this));
+ }
+
+ void DisplayCursor(const WebCursor& cursor) override {
+ current_cursor_ = cursor;
+ }
+
+ CursorManager* GetCursorManager() override { return cursor_manager_.get(); }
+
+ WebCursor cursor() { return current_cursor_; }
+
+ private:
+ WebCursor current_cursor_;
+ std::unique_ptr<CursorManager> cursor_manager_;
+};
+
+class CursorManagerTest : public testing::Test {
+ public:
+ CursorManagerTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+ void SetUp() override {
+ browser_context_.reset(new TestBrowserContext);
+ process_host_.reset(new MockRenderProcessHost(browser_context_.get()));
+ widget_host_.reset(MakeNewWidgetHost());
+ top_view_ =
+ new MockRenderWidgetHostViewForCursors(widget_host_.get(), true);
+ }
+
+ RenderWidgetHostImpl* MakeNewWidgetHost() {
+ int32_t routing_id = process_host_->GetNextRoutingID();
+ return new RenderWidgetHostImpl(&delegate_, process_host_.get(), routing_id,
+ false);
+ }
+
+ void TearDown() override {
+ if (top_view_)
+ delete top_view_;
+
+ widget_host_.reset();
+ process_host_.reset();
+ }
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ std::unique_ptr<BrowserContext> browser_context_;
+ std::unique_ptr<MockRenderProcessHost> process_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_TRUE(top_view_->cursor().IsEqual(WebCursor()));
+
+ CursorInfo cursor_info(blink::WebCursorInfo::kTypeHand);
+ WebCursor cursor_hand;
+ cursor_hand.InitFromCursorInfo(cursor_info);
+
+ // 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_TRUE(top_view_->cursor().IsEqual(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));
+
+ CursorInfo cursor_info(blink::WebCursorInfo::kTypeHand);
+ WebCursor cursor_hand;
+ cursor_hand.InitFromCursorInfo(cursor_info);
+
+ // 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_FALSE(top_view_->cursor().IsEqual(cursor_hand));
+
+ // Now moving the mouse over the child frame should update the overall cursor.
+ top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view.get());
+ EXPECT_TRUE(top_view_->cursor().IsEqual(cursor_hand));
+
+ // Destruction of the child view should restore the parent frame's cursor.
+ top_view_->GetCursorManager()->ViewBeingDestroyed(child_view.get());
+ EXPECT_FALSE(top_view_->cursor().IsEqual(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));
+
+ CursorInfo cursor_info_hand(blink::WebCursorInfo::kTypeHand);
+ WebCursor cursor_hand;
+ cursor_hand.InitFromCursorInfo(cursor_info_hand);
+
+ CursorInfo cursor_info_cross(blink::WebCursorInfo::kTypeCross);
+ WebCursor cursor_cross;
+ cursor_cross.InitFromCursorInfo(cursor_info_cross);
+
+ CursorInfo cursor_info_pointer(blink::WebCursorInfo::kTypePointer);
+ WebCursor cursor_pointer;
+ cursor_pointer.InitFromCursorInfo(cursor_info_pointer);
+
+ // 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_TRUE(top_view_->cursor().IsEqual(cursor_hand));
+
+ // Simulate moving the mouse between child views and receiving cursor updates.
+ top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view1.get());
+ EXPECT_TRUE(top_view_->cursor().IsEqual(cursor_cross));
+ top_view_->GetCursorManager()->UpdateViewUnderCursor(child_view2.get());
+ EXPECT_TRUE(top_view_->cursor().IsEqual(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_TRUE(top_view_->cursor().IsEqual(cursor_pointer));
+ top_view_->GetCursorManager()->UpdateCursor(child_view2.get(), cursor_cross);
+ EXPECT_TRUE(top_view_->cursor().IsEqual(cursor_cross));
+ top_view_->GetCursorManager()->UpdateCursor(top_view_, cursor_hand);
+ EXPECT_TRUE(top_view_->cursor().IsEqual(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_TRUE(top_view_->cursor().IsEqual(cursor_cross));
+ top_view_->GetCursorManager()->ViewBeingDestroyed(child_view2.get());
+ EXPECT_TRUE(top_view_->cursor().IsEqual(cursor_hand));
+}
+
+} // namespace content
+
+#endif // defined(USE_AURA) || defined(OS_MACOSX)
\ No newline at end of file
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index bb6b5ec..124d1f15 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -11,6 +11,7 @@
#include "cc/surfaces/surface_manager.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/frame_host/render_widget_host_view_guest.h"
+#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/common/frame_messages.h"
@@ -257,11 +258,15 @@
// SendMouseEnterOrLeaveEvents is called with the original event
// coordinates, which are transformed independently for each view that will
- // receive an event.
+ // receive an event. Also, since the view under the mouse has changed,
+ // notify the CursorManager that it might need to change the cursor.
if ((event->GetType() == blink::WebInputEvent::kMouseLeave ||
event->GetType() == blink::WebInputEvent::kMouseMove) &&
- target != last_mouse_move_target_)
+ target != last_mouse_move_target_) {
SendMouseEnterOrLeaveEvents(event, target, root_view);
+ if (root_view->GetCursorManager())
+ root_view->GetCursorManager()->UpdateViewUnderCursor(target);
+ }
event->SetPositionInWidget(transformed_point.x(), transformed_point.y());
target->ProcessMouseEvent(*event, latency);
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 9e158628..131f2c1 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -31,6 +31,7 @@
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/gpu/compositor_util.h"
+#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/delegated_frame_host_client_aura.h"
#include "content/browser/renderer_host/dip_util.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target_aura.h"
@@ -412,6 +413,8 @@
if (GetTextInputManager())
GetTextInputManager()->AddObserver(this);
+ cursor_manager_.reset(new CursorManager(this));
+
bool overscroll_enabled = base::CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
SetOverscrollControllerEnabled(overscroll_enabled);
@@ -782,6 +785,10 @@
}
void RenderWidgetHostViewAura::UpdateCursor(const WebCursor& cursor) {
+ GetCursorManager()->UpdateCursor(this, cursor);
+}
+
+void RenderWidgetHostViewAura::DisplayCursor(const WebCursor& cursor) {
current_cursor_ = cursor;
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
@@ -789,6 +796,10 @@
UpdateCursorIfOverSelf();
}
+CursorManager* RenderWidgetHostViewAura::GetCursorManager() {
+ return cursor_manager_.get();
+}
+
void RenderWidgetHostViewAura::SetIsLoading(bool is_loading) {
is_loading_ = is_loading;
UpdateCursorIfOverSelf();
@@ -1839,6 +1850,8 @@
selection_controller_.reset();
selection_controller_client_.reset();
+ GetCursorManager()->ViewBeingDestroyed(this);
+
delegated_frame_host_.reset();
window_observer_.reset();
if (window_) {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index f76bdd6..52b280e 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -68,6 +68,7 @@
class LegacyRenderWidgetHostHWND;
#endif
+class CursorManager;
class DelegatedFrameHost;
class DelegatedFrameHostClient;
class RenderFrameHostImpl;
@@ -126,6 +127,8 @@
void InitAsFullscreen(RenderWidgetHostView* reference_host_view) override;
void Focus() override;
void UpdateCursor(const WebCursor& cursor) override;
+ void DisplayCursor(const WebCursor& cursor) override;
+ CursorManager* GetCursorManager() override;
void SetIsLoading(bool is_loading) override;
void RenderProcessGone(base::TerminationStatus status,
int error_code) override;
@@ -607,6 +610,8 @@
viz::FrameSinkId frame_sink_id_;
viz::LocalSurfaceId local_surface_id_;
+ std::unique_ptr<CursorManager> cursor_manager_;
+
base::WeakPtrFactory<RenderWidgetHostViewAura> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAura);
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 8bdc035..ebf64e9 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -320,6 +320,14 @@
NOTIMPLEMENTED();
}
+void RenderWidgetHostViewBase::DisplayCursor(const WebCursor& cursor) {
+ return;
+}
+
+CursorManager* RenderWidgetHostViewBase::GetCursorManager() {
+ return nullptr;
+}
+
// static
ScreenOrientationValues RenderWidgetHostViewBase::GetOrientationTypeForMobile(
const display::Display& display) {
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index b4297aff..9a124eb 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -74,6 +74,7 @@
class BrowserAccessibilityDelegate;
class BrowserAccessibilityManager;
+class CursorManager;
class RenderWidgetHostImpl;
class RenderWidgetHostViewBaseObserver;
class SyntheticGestureTarget;
@@ -364,9 +365,18 @@
// helps to position the full screen widget on the correct monitor.
virtual void InitAsFullscreen(RenderWidgetHostView* reference_host_view) = 0;
- // Sets the cursor to the one associated with the specified cursor_type
+ // Sets the cursor for this view to the one associated with the specified
+ // cursor_type.
virtual void UpdateCursor(const WebCursor& cursor) = 0;
+ // Changes the cursor that is displayed on screen. This may or may not match
+ // the current cursor's view which was set by UpdateCursor.
+ virtual void DisplayCursor(const WebCursor& cursor);
+
+ // Views that manage cursors for window return a CursorManager. Other views
+ // return nullptr.
+ virtual CursorManager* GetCursorManager();
+
// Indicates whether the page has finished loading.
virtual void SetIsLoading(bool is_loading) = 0;
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index 1c2431f..fe03c9a 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -41,6 +41,7 @@
#include "ui/display/display_observer.h"
namespace content {
+class CursorManager;
class RenderWidgetHost;
class RenderWidgetHostImpl;
class RenderWidgetHostViewMac;
@@ -302,6 +303,8 @@
void InitAsFullscreen(RenderWidgetHostView* reference_host_view) override;
void Focus() override;
void UpdateCursor(const WebCursor& cursor) override;
+ void DisplayCursor(const WebCursor& cursor) override;
+ CursorManager* GetCursorManager() override;
void SetIsLoading(bool is_loading) override;
void RenderProcessGone(base::TerminationStatus status,
int error_code) override;
@@ -588,6 +591,8 @@
SkColor background_color_ = SK_ColorTRANSPARENT;
SkColor last_frame_root_background_color_ = SK_ColorTRANSPARENT;
+ std::unique_ptr<CursorManager> cursor_manager_;
+
// Factory used to safely scope delayed calls to ShutdownHost().
base::WeakPtrFactory<RenderWidgetHostViewMac> weak_factory_;
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 345343e..febff77c 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -42,6 +42,7 @@
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/gpu/compositor_util.h"
+#include "content/browser/renderer_host/cursor_manager.h"
#import "content/browser/renderer_host/input/synthetic_gesture_target_mac.h"
#include "content/browser/renderer_host/input/web_input_event_builders_mac.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
@@ -479,6 +480,8 @@
needs_begin_frames = !rvh->GetDelegate()->IsNeverVisible();
}
+ cursor_manager_.reset(new CursorManager(this));
+
if (GetTextInputManager())
GetTextInputManager()->AddObserver(this);
@@ -881,10 +884,18 @@
}
void RenderWidgetHostViewMac::UpdateCursor(const WebCursor& cursor) {
+ GetCursorManager()->UpdateCursor(this, cursor);
+}
+
+void RenderWidgetHostViewMac::DisplayCursor(const WebCursor& cursor) {
WebCursor web_cursor = cursor;
[cocoa_view_ updateCursor:web_cursor.GetNativeCursor()];
}
+CursorManager* RenderWidgetHostViewMac::GetCursorManager() {
+ return cursor_manager_.get();
+}
+
void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
is_loading_ = is_loading;
// If we ever decide to show the waiting cursor while the page is loading
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index ac68083..178d504 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -41,6 +41,7 @@
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/input/input_router.h"
#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
@@ -5550,9 +5551,16 @@
// Verify that we receive a mouse cursor update message when we mouse over
// a text field contained in an out-of-process iframe.
-// Fails under TSan. http://crbug.com/545237
+#if defined(OS_ANDROID)
+// Android does not have mouse cursors.
+#define MAYBE_CursorUpdateReceivedFromCrossSiteIframe \
+ DISABLED_CursorUpdateReceivedFromCrossSiteIframe
+#else
+#define MAYBE_CursorUpdateReceivedFromCrossSiteIframe \
+ CursorUpdateReceivedCrossSiteIframe
+#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
- DISABLED_CursorUpdateFromReceivedFromCrossSiteIframe) {
+ MAYBE_CursorUpdateReceivedFromCrossSiteIframe) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -5563,19 +5571,33 @@
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
+ WaitForChildFrameSurfaceReady(child_node->current_frame_host());
+
scoped_refptr<CursorMessageFilter> filter = new CursorMessageFilter();
child_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
+ RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
+ root->current_frame_host()->GetRenderWidgetHost()->GetView());
+ RenderWidgetHost* rwh_child =
+ root->child_at(0)->current_frame_host()->GetRenderWidgetHost();
+ RenderWidgetHostViewBase* child_view =
+ static_cast<RenderWidgetHostViewBase*>(rwh_child->GetView());
+
+ // This should only return nullptr on Android.
+ EXPECT_TRUE(root_view->GetCursorManager());
+
+ WebCursor cursor;
+ EXPECT_FALSE(
+ root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor));
+ EXPECT_FALSE(
+ root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor));
+
// Send a MouseMove to the subframe. The frame contains text, and moving the
// mouse over it should cause the renderer to send a mouse cursor update.
blink::WebMouseEvent mouse_event(blink::WebInputEvent::kMouseMove,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::kTimeStampForTesting);
mouse_event.SetPositionInWidget(60, 60);
- RenderWidgetHost* rwh_child =
- root->child_at(0)->current_frame_host()->GetRenderWidgetHost();
- RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
- root->current_frame_host()->GetRenderWidgetHost()->GetView());
web_contents()->GetInputEventRouter()->RouteMouseEvent(
root_view, &mouse_event, ui::LatencyInfo());
@@ -5584,6 +5606,24 @@
// does not return otherwise.
filter->Wait();
EXPECT_EQ(filter->last_set_cursor_routing_id(), rwh_child->GetRoutingID());
+
+ // Yield to ensure that the SetCursor message is processed by its real
+ // handler.
+ {
+ base::RunLoop loop;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ loop.QuitClosure());
+ loop.Run();
+ }
+
+ EXPECT_FALSE(
+ root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor));
+ EXPECT_TRUE(
+ root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor));
+ // Since this moused over a text box, this should not be the default cursor.
+ CursorInfo cursor_info;
+ cursor.GetCursorInfo(&cursor_info);
+ EXPECT_EQ(cursor_info.type, blink::WebCursorInfo::kTypeIBeam);
}
#endif
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f9e323f..65c23fb 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1246,6 +1246,7 @@
"../browser/presentation/presentation_service_impl_unittest.cc",
"../browser/renderer_host/clipboard_message_filter_unittest.cc",
"../browser/renderer_host/compositor_resize_lock_unittest.cc",
+ "../browser/renderer_host/cursor_manager_unittest.cc",
"../browser/renderer_host/dwrite_font_proxy_message_filter_win_unittest.cc",
"../browser/renderer_host/input/gesture_event_queue_unittest.cc",
"../browser/renderer_host/input/legacy_input_router_impl_unittest.cc",