// Copyright 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 #include "content/browser/pointer_lock_browsertest.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/render_widget_host_input_event_router.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/web_contents_delegate.h" #include "content/public/common/content_features.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/public/test/hit_test_region_observer.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/content_browser_test_utils_internal.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "ui/base/ui_base_features.h" #ifdef USE_AURA #include "content/browser/renderer_host/render_widget_host_view_aura.h" #include "content/browser/web_contents/web_contents_view_aura.h" #endif // USE_AURA namespace content { class MockPointerLockWebContentsDelegate : public WebContentsDelegate { public: MockPointerLockWebContentsDelegate() {} ~MockPointerLockWebContentsDelegate() override {} void RequestToLockMouse(WebContents* web_contents, bool user_gesture, bool last_unlocked_by_target) override { if (user_gesture) web_contents->GotResponseToLockMouseRequest( blink::mojom::PointerLockResult::kSuccess); else web_contents->GotResponseToLockMouseRequest( blink::mojom::PointerLockResult::kRequiresUserGesture); } void LostMouseLock() override {} }; #ifdef USE_AURA class ScopedEnableUnadjustedMouseEventsForTesting : public aura::ScopedEnableUnadjustedMouseEvents { public: explicit ScopedEnableUnadjustedMouseEventsForTesting() {} ~ScopedEnableUnadjustedMouseEventsForTesting() override {} }; class MockPointerLockRenderWidgetHostView : public RenderWidgetHostViewAura { public: MockPointerLockRenderWidgetHostView(RenderWidgetHost* host) : RenderWidgetHostViewAura(host), host_(RenderWidgetHostImpl::From(host)) {} ~MockPointerLockRenderWidgetHostView() override { if (IsMouseLocked()) UnlockMouse(); } blink::mojom::PointerLockResult LockMouse( bool request_unadjusted_movement) override { event_handler()->mouse_locked_ = true; event_handler()->mouse_locked_unadjusted_movement_ = request_unadjusted_movement ? std::make_unique() : nullptr; return blink::mojom::PointerLockResult::kSuccess; } void UnlockMouse() override { host_->LostMouseLock(); event_handler()->mouse_locked_ = false; event_handler()->mouse_locked_unadjusted_movement_.reset(); } bool GetIsMouseLockedUnadjustedMovementForTesting() override { return IsMouseLocked() && event_handler()->mouse_locked_unadjusted_movement_; } void OnWindowFocused(aura::Window* gained_focus, aura::Window* lost_focus) override { // Ignore window focus events. } bool IsMouseLocked() override { return event_handler()->mouse_locked(); } bool HasFocus() override { return has_focus_; } RenderWidgetHostImpl* host_; bool has_focus_ = true; }; void InstallCreateHooksForPointerLockBrowserTests() { WebContentsViewAura::InstallCreateHookForTests( [](RenderWidgetHost* host) -> RenderWidgetHostViewAura* { return new MockPointerLockRenderWidgetHostView(host); }); } #endif // USE_AURA class PointerLockBrowserTest : public ContentBrowserTest { public: PointerLockBrowserTest() {} protected: void SetUpCommandLine(base::CommandLine* command_line) override { IsolateAllSitesForTesting(command_line); } void SetUp() override { InstallCreateHooksForPointerLockBrowserTests(); ContentBrowserTest::SetUp(); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); web_contents()->SetDelegate(&web_contents_delegate_); } WebContentsImpl* web_contents() const { return static_cast(shell()->web_contents()); } protected: MockPointerLockWebContentsDelegate web_contents_delegate_; }; class PointerLockBrowserTestWithOptions : public PointerLockBrowserTest { public: PointerLockBrowserTestWithOptions() { feature_list_.InitAndEnableFeature(features::kPointerLockOptions); } private: base::test::ScopedFeatureList feature_list_; }; namespace { class PointerLockHelper { public: // requestPointerLock is an asynchronous operation. This method returns when // document.body.requestPointerLock() either succeeds or fails. // Returns true if Pointer Lock on body was successful. static EvalJsResult RequestPointerLockOnBody( const ToRenderFrameHost& execution_target, const int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS) { return EvalJs(execution_target, set_pointer_lock_promise_ + "document.body.requestPointerLock();" + wait_for_pointer_lock_promise_, options); } static EvalJsResult RequestPointerLockWithUnadjustedMovementOnBody( const ToRenderFrameHost& execution_target, const int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS) { return EvalJs( execution_target, set_pointer_lock_promise_ + "document.body.requestPointerLock({unadjustedMovement:true});" + wait_for_pointer_lock_promise_, options); } // exitPointerLock is an asynchronous operation. This method returns when // document.exitPointerLock() either succeeds or fails. // Returns true if Exit Pointer Lock was successful static EvalJsResult ExitPointerLock( const ToRenderFrameHost& execution_target, const int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS) { return EvalJs(execution_target, set_pointer_lock_promise_ + "document.exitPointerLock();" + wait_for_pointer_lock_promise_, options); } static EvalJsResult IsPointerLockOnBody( const ToRenderFrameHost& execution_target, const int options = EXECUTE_SCRIPT_DEFAULT_OPTIONS) { return EvalJs(execution_target, "document.pointerLockElement === document.body", options); } private: static const std::string set_pointer_lock_promise_; static const std::string wait_for_pointer_lock_promise_; }; // static const std::string PointerLockHelper::set_pointer_lock_promise_ = R"code(pointerLockPromise=new Promise(function (resolve, reject){ document.addEventListener('pointerlockchange', resolve); document.addEventListener('pointerlockerror', reject); });)code"; // static const std::string PointerLockHelper::wait_for_pointer_lock_promise_ = "(async()=> {return await pointerLockPromise.then(()=>true, " "()=>false);})()"; } // namespace IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockBasic) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); // Request a pointer lock on the root frame's body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(root)); EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Request a pointer lock on the child frame's body. EXPECT_EQ(false, PointerLockHelper::RequestPointerLockOnBody(child)); // Child frame should not be granted pointer lock since the root frame has it. EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody(child)); // Release pointer lock on root frame. EXPECT_EQ(true, PointerLockHelper::ExitPointerLock(root)); // Request a pointer lock on the child frame's body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // ensure request finishes before moving on. // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); } IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockAndUserActivation) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b(b))")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); FrameTreeNode* grand_child = child->child_at(0); // Without user activation, pointer lock request from any (child or // grand_child) frame fails. EXPECT_EQ(false, PointerLockHelper::RequestPointerLockOnBody( child, EXECUTE_SCRIPT_NO_USER_GESTURE)); EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody( child, EXECUTE_SCRIPT_NO_USER_GESTURE)); EXPECT_EQ(false, PointerLockHelper::RequestPointerLockOnBody( grand_child, EXECUTE_SCRIPT_NO_USER_GESTURE)); EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody( grand_child, EXECUTE_SCRIPT_NO_USER_GESTURE)); // Execute a empty (dummy) JS to activate the child frame. EXPECT_TRUE(ExecJs(child, "")); // With user activation in the child frame, pointer lock from the same frame // succeeds. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody( child, EXECUTE_SCRIPT_NO_USER_GESTURE)); EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody( child, EXECUTE_SCRIPT_NO_USER_GESTURE)); // But with user activation in the child frame, pointer lock from the // grand_child frame fails. EXPECT_EQ(false, PointerLockHelper::RequestPointerLockOnBody( grand_child, EXECUTE_SCRIPT_NO_USER_GESTURE)); EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody( grand_child, EXECUTE_SCRIPT_NO_USER_GESTURE)); } IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockEventRouting) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); RenderWidgetHostInputEventRouter* router = web_contents()->GetInputEventRouter(); RenderWidgetHostViewBase* root_view = static_cast( root->current_frame_host()->GetView()); RenderWidgetHostViewBase* child_view = static_cast( child->current_frame_host()->GetView()); WaitForHitTestData(child->current_frame_host()); std::string set_mouse_move_event_listener = R"code(mouseMoveExecuted = new Promise(function (resolve, reject){ mousemoveHandler = function(e){ x = e.x; y = e.y; mX = e.movementX; mY = e.movementY; document.removeEventListener('mousemove', mousemoveHandler); resolve(); }; document.addEventListener('mousemove', mousemoveHandler); });)code"; std::string define_variables = R"code(var x; var y; var mX; var mY; var mouseMoveExecuted; var mousemoveHandler; )code"; // Add a mouse move event listener to the root frame. EXPECT_TRUE(ExecJs(root, define_variables)); EXPECT_TRUE(ExecJs(root, set_mouse_move_event_listener)); // Send a mouse move to root frame before lock to set last mouse position. blink::WebMouseEvent mouse_event( blink::WebInputEvent::Type::kMouseMove, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.pointer_type = blink::WebPointerProperties::PointerType::kMouse; mouse_event.SetPositionInWidget(6, 7); mouse_event.SetPositionInScreen(6, 7); mouse_event.movement_x = 8; mouse_event.movement_y = 9; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); // wait for mouse move to fire mouse move event EXPECT_EQ(true, EvalJs(root, "(async ()=> {return await " "mouseMoveExecuted.then(()=>true);})();")); if (base::FeatureList::IsEnabled(features::kConsolidatedMovementXY)) EXPECT_EQ("[6,7,0,0]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); else EXPECT_EQ("[6,7,8,9]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); EXPECT_TRUE(ExecJs(root, set_mouse_move_event_listener)); mouse_event.SetPositionInWidget(10, 12); mouse_event.SetPositionInScreen(10, 12); mouse_event.movement_x = 12; mouse_event.movement_y = 13; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); EXPECT_EQ(true, EvalJs(root, "(async ()=> {return await " "mouseMoveExecuted.then(()=>true);})();")); // Locked event has same coordinates as before locked. if (base::FeatureList::IsEnabled(features::kConsolidatedMovementXY)) EXPECT_EQ("[6,7,4,5]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); else EXPECT_EQ("[6,7,12,13]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); EXPECT_EQ(true, PointerLockHelper::ExitPointerLock(root)); EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // define all all global variables on the child EXPECT_TRUE(ExecJs(child, define_variables)); // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); // Add a mouse move event listener to the child frame. EXPECT_TRUE(ExecJs(child, set_mouse_move_event_listener)); gfx::PointF transformed_point; root_view->TransformPointToCoordSpaceForView(gfx::PointF(0, 0), child_view, &transformed_point); mouse_event.SetPositionInWidget(-transformed_point.x() + 14, -transformed_point.y() + 15); mouse_event.SetPositionInScreen(-transformed_point.x() + 14, -transformed_point.y() + 15); mouse_event.movement_x = 16; mouse_event.movement_y = 17; // We use root_view intentionally as the RenderWidgetHostInputEventRouter is // responsible for correctly routing the event to the child frame. router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); EXPECT_EQ(true, EvalJs(child, "(async ()=> {return await " "mouseMoveExecuted.then(()=>true);})()")); // This is the first event to child render, so the coordinates is (0, 0) if (base::FeatureList::IsEnabled(features::kConsolidatedMovementXY)) EXPECT_EQ("[0,0,0,0]", EvalJs(child, "JSON.stringify([x,y,mX,mY])")); else EXPECT_EQ("[0,0,16,17]", EvalJs(child, "JSON.stringify([x,y,mX,mY])")); } // Tests that the browser will not unlock the pointer if a RenderWidgetHostView // that doesn't hold the pointer lock is destroyed IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockChildFrameDetached) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); // Request a pointer lock on the root frame's body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Root (platform) RenderWidgetHostView should have the pointer locked. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); EXPECT_EQ(root->current_frame_host()->GetRenderWidgetHost(), web_contents()->GetMouseLockWidget()); // Detach the child frame. EXPECT_TRUE(ExecJs(root, "document.querySelector('iframe').remove()")); // Root (platform) RenderWidgetHostView should still have the pointer locked. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); EXPECT_EQ(root->current_frame_host()->GetRenderWidgetHost(), web_contents()->GetMouseLockWidget()); } // Tests that the browser will unlock the pointer if a RenderWidgetHostView that // holds the pointer lock crashes. IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockInnerContentsCrashes) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b(b))")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); // Attach an inner WebContents; it's owned by the FrameTree, so we obtain an // observer to it. WebContents* inner_contents = CreateAndAttachInnerContents( root->child_at(0)->child_at(0)->current_frame_host()); WebContentsDestroyedWatcher inner_death_observer(inner_contents); // Override the delegate so that we can stub out pointer lock events. inner_contents->SetDelegate(&web_contents_delegate_); // Navigate the inner webcontents to a page. EXPECT_TRUE(NavigateToURLFromRenderer( inner_contents, embedded_test_server()->GetURL( "c.com", "/cross_site_iframe_factory.html?c(d)"))); // Request a pointer lock to the inner WebContents's document.body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody( inner_contents->GetMainFrame())); EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody( inner_contents->GetMainFrame())); // Root (platform) RenderWidgetHostView should have the pointer locked. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); // The widget doing the lock is the one from the inner WebContents. A link // to that RWH is saved into the outer webcontents. RenderWidgetHost* expected_lock_widget = inner_contents->GetMainFrame()->GetView()->GetRenderWidgetHost(); EXPECT_EQ(expected_lock_widget, web_contents()->GetMouseLockWidget()); EXPECT_EQ(expected_lock_widget, web_contents()->mouse_lock_widget_); EXPECT_EQ(expected_lock_widget, static_cast(inner_contents)->mouse_lock_widget_); // Crash the subframe process. RenderProcessHost* crash_process = root->child_at(0)->current_frame_host()->GetProcess(); RenderProcessHostWatcher crash_observer( crash_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); crash_process->Shutdown(0); crash_observer.Wait(); // Wait for destruction of |inner_contents|. inner_death_observer.Wait(); inner_contents = nullptr; // This should cancel the pointer lock. EXPECT_EQ(nullptr, web_contents()->GetMouseLockWidget()); EXPECT_EQ(nullptr, web_contents()->mouse_lock_widget_); EXPECT_FALSE(web_contents()->HasMouseLock( root->current_frame_host()->GetRenderWidgetHost())); } IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockOopifCrashes) { // This test runs three times, testing a crash at each level of the frametree. for (int crash_depth = 0; crash_depth < 3; crash_depth++) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b(c))")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* lock_node = root->child_at(0)->child_at(0); // Pick which node to crash. FrameTreeNode* crash_node = root; for (int i = 0; i < crash_depth; i++) crash_node = crash_node->child_at(0); // Request a pointer lock to |lock_node|'s document.body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(lock_node)); EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(lock_node)); // Root (platform) RenderWidgetHostView should have the pointer locked. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); EXPECT_EQ(lock_node->current_frame_host()->GetRenderWidgetHost(), web_contents()->GetMouseLockWidget()); // Crash the process of |crash_node|. RenderProcessHost* crash_process = crash_node->current_frame_host()->GetProcess(); RenderProcessHostWatcher crash_observer( crash_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); crash_process->Shutdown(0); crash_observer.Wait(); // This should cancel the pointer lock. EXPECT_EQ(nullptr, web_contents()->GetMouseLockWidget()); EXPECT_EQ(nullptr, web_contents()->mouse_lock_widget_); EXPECT_FALSE(web_contents()->HasMouseLock( root->current_frame_host()->GetRenderWidgetHost())); if (crash_depth != 0) EXPECT_FALSE(root->current_frame_host()->GetView()->IsMouseLocked()); else EXPECT_EQ(nullptr, root->current_frame_host()->GetView()); } } IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockWheelEventRouting) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); RenderWidgetHostInputEventRouter* router = web_contents()->GetInputEventRouter(); RenderWidgetHostViewBase* root_view = static_cast( root->current_frame_host()->GetView()); RenderWidgetHostViewBase* child_view = static_cast( child->current_frame_host()->GetView()); WaitForHitTestData(child->current_frame_host()); // Add a mouse move event listener to the root frame. EXPECT_TRUE(ExecJs( root, "var x; var y; var dX; var dY; document.addEventListener('mousemove', " "function(e) {x = e.x; y = e.y; mX = e.movementX; mY = e.movementY;});")); // Send a mouse move to root frame before lock to set last mouse position. blink::WebMouseEvent mouse_event( blink::WebInputEvent::Type::kMouseMove, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.pointer_type = blink::WebPointerProperties::PointerType::kMouse; mouse_event.SetPositionInWidget(6, 7); mouse_event.SetPositionInScreen(6, 7); mouse_event.movement_x = 8; mouse_event.movement_y = 9; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); // Make sure that the renderer handled the input event. MainThreadFrameObserver root_observer(root_view->GetRenderWidgetHost()); root_observer.Wait(); if (base::FeatureList::IsEnabled(features::kConsolidatedMovementXY)) EXPECT_EQ("[6,7,0,0]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); else EXPECT_EQ("[6,7,8,9]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Add a mouse move wheel event listener to the root frame. EXPECT_TRUE(ExecJs( root, "var x; var y; var dX; var dY; document.addEventListener('mousewheel', " "function(e) {x = e.x; y = e.y; dX = e.deltaX; dY = e.deltaY;});")); root_observer.Wait(); blink::WebMouseWheelEvent wheel_event( blink::WebInputEvent::Type::kMouseWheel, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); wheel_event.SetPositionInScreen(10, 11); wheel_event.delta_x = -12; wheel_event.delta_y = -13; wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; router->RouteMouseWheelEvent(root_view, &wheel_event, ui::LatencyInfo()); // Make sure that the renderer handled the input event. root_observer.Wait(); // All wheel events during a scroll sequence will be sent to a single target. // Send a wheel end event to the current target before sending wheel events to // a new target. wheel_event.delta_x = 0; wheel_event.delta_y = 0; wheel_event.phase = blink::WebMouseWheelEvent::kPhaseEnded; router->RouteMouseWheelEvent(root_view, &wheel_event, ui::LatencyInfo()); // Make sure that the renderer handled the input event. root_observer.Wait(); // Locked event has same coordinates as before locked. EXPECT_EQ("[6,7,12,13]", EvalJs(root, "JSON.stringify([x, y, dX, dY])")); EXPECT_EQ(true, PointerLockHelper::ExitPointerLock(root)); EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); // Add a mouse move event listener to the child frame. EXPECT_TRUE(ExecJs( child, "var x; var y; var dX; var dY; document.addEventListener('mousewheel', " "function(e) {x = e.x; y = e.y; dX = e.deltaX; dY = e.deltaY;});")); MainThreadFrameObserver child_observer(child_view->GetRenderWidgetHost()); child_observer.Wait(); gfx::PointF transformed_point; root_view->TransformPointToCoordSpaceForView(gfx::PointF(0, 0), child_view, &transformed_point); wheel_event.SetPositionInWidget(-transformed_point.x() + 14, -transformed_point.y() + 15); wheel_event.SetPositionInScreen(-transformed_point.x() + 14, -transformed_point.y() + 15); wheel_event.delta_x = -16; wheel_event.delta_y = -17; wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; // We use root_view intentionally as the RenderWidgetHostInputEventRouter is // responsible for correctly routing the event to the child frame. router->RouteMouseWheelEvent(root_view, &wheel_event, ui::LatencyInfo()); // Make sure that the renderer handled the input event. child_observer.Wait(); // This is the first event to child render, so the coordinates is (0, 0) EXPECT_EQ("[0,0,16,17]", EvalJs(child, "JSON.stringify([x, y, dX, dY])")); } IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockWidgetHidden) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); RenderWidgetHostViewBase* child_view = static_cast( child->current_frame_host()->GetView()); WaitForHitTestData(child->current_frame_host()); // Request a pointer lock on the child frame's body. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); EXPECT_TRUE(child_view->IsMouseLocked()); EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget()); child_view->Hide(); // Child frame should've released the mouse lock when hidden. EXPECT_FALSE(child_view->IsMouseLocked()); EXPECT_EQ(nullptr, web_contents()->GetMouseLockWidget()); } #ifdef USE_AURA IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLockOutOfFocus) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); MockPointerLockRenderWidgetHostView* root_view = static_cast( root->current_frame_host()->GetView()); root_view->has_focus_ = false; // Request a pointer lock on the root frame's body. EXPECT_EQ(false, PointerLockHelper::RequestPointerLockOnBody(root)); // Root frame should not have been granted pointer lock. EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody(root)); } #endif IN_PROC_BROWSER_TEST_F(PointerLockBrowserTestWithOptions, PointerLockRequestUnadjustedMovement) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); EXPECT_TRUE(ExecJs(root, "var pointerLockPromise;")); std::string wait_for_pointer_lock_promise = "(async ()=> {return await pointerLockPromise.then(()=>true, " "()=>false);})()"; std::string set_pointer_lock_promise = R"code(pointerLockPromise = new Promise( function(resolve, reject){ document.addEventListener('pointerlockchange', resolve); document.addEventListener('pointerlockerror', reject) });)code"; // Request a pointer lock. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Mouse is locked and unadjusted_movement is not set. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); // Release pointer lock. EXPECT_EQ(true, PointerLockHelper::ExitPointerLock(root)); #if defined(USE_AURA) || defined(OS_MAC) // Request a pointer lock with unadjustedMovement. EXPECT_EQ( true, PointerLockHelper::RequestPointerLockWithUnadjustedMovementOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Mouse is locked and unadjusted_movement is set. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); EXPECT_TRUE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); // Release pointer lock, unadjusted_movement bit is reset. EXPECT_EQ(true, PointerLockHelper::ExitPointerLock(root)); EXPECT_FALSE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); #else // Request a pointer lock with unadjustedMovement. // On platform that does not support unadjusted movement yet, do not lock and // a pointerlockerror event is dispatched. EXPECT_EQ( false, PointerLockHelper::RequestPointerLockWithUnadjustedMovementOnBody(root)); EXPECT_EQ(false, PointerLockHelper::IsPointerLockOnBody(root)); EXPECT_FALSE(root->current_frame_host()->GetView()->IsMouseLocked()); #endif } #if defined(USE_AURA) IN_PROC_BROWSER_TEST_F(PointerLockBrowserTestWithOptions, UnadjustedMovement) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); RenderWidgetHostInputEventRouter* router = web_contents()->GetInputEventRouter(); RenderWidgetHostViewBase* root_view = static_cast( root->current_frame_host()->GetView()); // Add a mouse move event listener to the root frame. EXPECT_TRUE(ExecJs( root, "var x; var y; var mX; var mY; document.addEventListener('mousemove', " "function(e) {x = e.x; y = e.y; mX = e.movementX; mY = e.movementY;});")); // Send a mouse move to root frame before lock. blink::WebMouseEvent mouse_event( blink::WebInputEvent::Type::kMouseMove, blink::WebInputEvent::kNoModifiers, blink::WebInputEvent::GetStaticTimeStampForTests()); mouse_event.pointer_type = blink::WebPointerProperties::PointerType::kMouse; mouse_event.SetPositionInWidget(6, 7); mouse_event.SetPositionInScreen(6, 7); mouse_event.movement_x = 8; mouse_event.movement_y = 9; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); // Make sure that the renderer handled the input event. MainThreadFrameObserver root_observer(root_view->GetRenderWidgetHost()); root_observer.Wait(); EXPECT_EQ("[6,7,0,0]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); // Request a pointer lock with unadjustedMovement. EXPECT_EQ( true, PointerLockHelper::RequestPointerLockWithUnadjustedMovementOnBody(root)); // Root frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(root)); // Mouse is locked and unadjusted_movement is not set. EXPECT_TRUE(root->current_frame_host()->GetView()->IsMouseLocked()); mouse_event.SetPositionInWidget(10, 10); mouse_event.SetPositionInScreen(10, 10); mouse_event.movement_x = 12; mouse_event.movement_y = 9; mouse_event.is_raw_movement_event = true; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); root_observer.Wait(); // Raw movement events movement value from WebMouseEvent.movement_x/y. EXPECT_EQ("[6,7,12,9]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); mouse_event.SetPositionInWidget(20, 21); mouse_event.SetPositionInScreen(20, 21); mouse_event.movement_x = 1; mouse_event.movement_y = 2; mouse_event.is_raw_movement_event = false; router->RouteMouseEvent(root_view, &mouse_event, ui::LatencyInfo()); root_observer.Wait(); // Non-raw movement events movement value from screen pos - last screen pos. EXPECT_EQ("[6,7,10,11]", EvalJs(root, "JSON.stringify([x,y,mX,mY])")); } #endif #if defined(USE_AURA) // TODO(https://crbug.com/982379): Remove failure test when fully implemented #if defined(OS_WIN) || defined(OS_CHROMEOS) #define MAYBE_ChangeUnadjustedMovementFailure \ DISABLED_ChangeUnadjustedMovementFailure #else #define MAYBE_ChangeUnadjustedMovementFailure ChangeUnadjustedMovementFailure #endif // Tests that a subsequent request to RequestPointerLock with different // options inside a Child view gets piped to the proper places and gives // the proper unsupported error(this option is only supported on Windows // This was prompted by this bug: https://crbug.com/1062702 IN_PROC_BROWSER_TEST_F(PointerLockBrowserTestWithOptions, MAYBE_ChangeUnadjustedMovementFailure) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); RenderWidgetHostViewBase* child_view = static_cast( child->current_frame_host()->GetView()); WaitForHitTestData(child->current_frame_host()); // Request a pointer lock on the child frame's body and wait for the promise // to resolve. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); EXPECT_TRUE(child_view->IsMouseLocked()); EXPECT_FALSE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget()); // Request to change pointer lock options and wait for return. EXPECT_EQ( "a JavaScript error: \"NotSupportedError: The options asked for in this " "request are not supported on this platform.\"\n", EvalJs(child, "document.body.requestPointerLock({unadjustedMovement:true})") .error); // The change errored out but the original lock should still be in place. EXPECT_TRUE(child_view->IsMouseLocked()); EXPECT_FALSE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget()); } #endif #if defined(USE_AURA) #if defined(OS_WIN) // Tests that a subsequent request to RequestPointerLock with different // options inside a Child view gets piped to the proper places and updates // the option(this option is only supported on Windows). // This was prompted by this bug: https://crbug.com/1062702 IN_PROC_BROWSER_TEST_F(PointerLockBrowserTestWithOptions, ChangeUnadjustedMovementSuccess) { GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b)")); EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); FrameTreeNode* child = root->child_at(0); RenderWidgetHostViewBase* child_view = static_cast( child->current_frame_host()->GetView()); WaitForHitTestData(child->current_frame_host()); // Request a pointer lock on the child frame's body and wait for the promise // to resolve. EXPECT_EQ(true, PointerLockHelper::RequestPointerLockOnBody(child)); // Child frame should have been granted pointer lock. EXPECT_EQ(true, PointerLockHelper::IsPointerLockOnBody(child)); EXPECT_TRUE(child_view->IsMouseLocked()); EXPECT_FALSE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget()); // Request to change pointer lock options and wait for return. EXPECT_EQ( nullptr, EvalJs(child, "document.body.requestPointerLock({unadjustedMovement:true})")); // The new changed lock should now be in place. EXPECT_TRUE(child_view->IsMouseLocked()); EXPECT_TRUE(root->current_frame_host() ->GetView() ->GetIsMouseLockedUnadjustedMovementForTesting()); EXPECT_EQ(child_view->host(), web_contents()->GetMouseLockWidget()); } #endif // WIN_OS #endif // USE_AURA } // namespace content