| // 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 <string> |
| |
| #include "content/browser/pointer_lock_browsertest.h" |
| |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "content/browser/frame_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<ScopedEnableUnadjustedMouseEventsForTesting>() |
| : 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<WebContentsImpl*>(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 |
| |
| // crbug.com/1060129 |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, DISABLED_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)); |
| } |
| |
| // crbug.com/1060129 |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, |
| DISABLED_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)); |
| } |
| |
| // Flaky test crbug.com/1077306 |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, |
| DISABLED_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<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| 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. |
| // crbug.com/1060129 |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, |
| DISABLED_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<WebContentsImpl*>(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()); |
| } |
| } |
| |
| // Flaky test https://crbug.com/766450 |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, |
| DISABLED_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<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| 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<RenderWidgetHostViewBase*>( |
| 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<MockPointerLockRenderWidgetHostView*>( |
| 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) |
| // 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) |
| #if defined(OS_WIN) || defined(OS_LINUX) |
| // https://crbug.com/1043985#c9: Flaky on Windows and Linux. |
| #define MAYBE_UnadjustedMovement DISABLED_UnadjustedMovement |
| #else |
| #define MAYBE_UnadjustedMovement UnadjustedMovement |
| #endif |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTestWithOptions, |
| MAYBE_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<RenderWidgetHostViewBase*>( |
| 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) |
| #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<RenderWidgetHostViewBase*>( |
| 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<RenderWidgetHostViewBase*>( |
| 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 |