| // 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 "content/browser/pointer_lock_browsertest.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/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/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" |
| |
| #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 { |
| |
| namespace { |
| |
| #ifdef USE_AURA |
| class MockRenderWidgetHostView : public RenderWidgetHostViewAura { |
| public: |
| MockRenderWidgetHostView(RenderWidgetHost* host, bool is_guest_view_hack) |
| : RenderWidgetHostViewAura(host, |
| is_guest_view_hack, |
| false /* is_mus_browser_plugin_guest */), |
| host_(RenderWidgetHostImpl::From(host)) {} |
| ~MockRenderWidgetHostView() override { |
| if (mouse_locked_) |
| UnlockMouse(); |
| } |
| |
| bool LockMouse() override { |
| mouse_locked_ = true; |
| return true; |
| } |
| |
| void UnlockMouse() override { |
| host_->LostMouseLock(); |
| mouse_locked_ = false; |
| } |
| |
| bool IsMouseLocked() override { return mouse_locked_; } |
| |
| bool HasFocus() const override { return true; } |
| |
| void OnWindowFocused(aura::Window* gained_focus, |
| aura::Window* lost_focus) override { |
| // Ignore window focus events. |
| } |
| |
| RenderWidgetHostImpl* host_; |
| }; |
| #endif // USE_AURA |
| |
| } // namespace |
| |
| class MockPointerLockWebContentsDelegate : public WebContentsDelegate { |
| public: |
| MockPointerLockWebContentsDelegate() {} |
| ~MockPointerLockWebContentsDelegate() override {} |
| |
| void RequestToLockMouse(WebContents* web_contents, |
| bool user_gesture, |
| bool last_unlocked_by_target) override { |
| web_contents->GotResponseToLockMouseRequest(true); |
| } |
| |
| void LostMouseLock() override {} |
| }; |
| |
| #ifdef USE_AURA |
| void InstallCreateHooksForPointerLockBrowserTests() { |
| WebContentsViewAura::InstallCreateHookForTests( |
| [](RenderWidgetHost* host, |
| bool is_guest_view_hack) -> RenderWidgetHostViewAura* { |
| return new MockRenderWidgetHostView(host, is_guest_view_hack); |
| }); |
| } |
| #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()); |
| } |
| |
| private: |
| MockPointerLockWebContentsDelegate web_contents_delegate_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PointerLockBrowserTest, PointerLock) { |
| 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_TRUE(ExecuteScript(root, "document.body.requestPointerLock()")); |
| |
| // Root frame should have been granted pointer lock. |
| bool locked = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(root, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // Request a pointer lock on the child frame's body. |
| EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()")); |
| |
| // Child frame should not be granted pointer lock since the root frame has it. |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(child, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_FALSE(locked); |
| |
| // Release pointer lock on root frame. |
| EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()")); |
| |
| // Request a pointer lock on the child frame's body. |
| EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()")); |
| |
| // Child frame should have been granted pointer lock. |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(child, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| } |
| |
| 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<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| child->current_frame_host()->GetView()); |
| |
| WaitForChildFrameSurfaceReady(child->current_frame_host()); |
| |
| // Request a pointer lock on the root frame's body. |
| EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()")); |
| |
| // Root frame should have been granted pointer lock. |
| bool locked = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(root, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // Add a mouse move event listener to the root frame. |
| EXPECT_TRUE(ExecuteScript( |
| 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;});")); |
| |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| mouse_event.SetPositionInWidget(10, 11); |
| mouse_event.movement_x = 12; |
| mouse_event.movement_y = 13; |
| 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(); |
| |
| int x, y, movementX, movementY; |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(x);", &x)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(y);", &y)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(mX);", &movementX)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(mY);", &movementY)); |
| EXPECT_EQ(10, x); |
| EXPECT_EQ(11, y); |
| EXPECT_EQ(12, movementX); |
| EXPECT_EQ(13, movementY); |
| |
| // Release pointer lock on root frame. |
| EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()")); |
| |
| // Request a pointer lock on the child frame's body. |
| EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()")); |
| |
| // Child frame should have been granted pointer lock. |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(child, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // Add a mouse move event listener to the child frame. |
| EXPECT_TRUE(ExecuteScript( |
| child, |
| "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;});")); |
| |
| 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.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()); |
| |
| // Make sure that the renderer handled the input event. |
| MainThreadFrameObserver child_observer(child_view->GetRenderWidgetHost()); |
| child_observer.Wait(); |
| |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(x);", &x)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(y);", &y)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(mX);", &movementX)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(mY);", &movementY)); |
| EXPECT_EQ(14, x); |
| EXPECT_EQ(15, y); |
| EXPECT_EQ(16, movementX); |
| EXPECT_EQ(17, movementY); |
| } |
| |
| // 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_TRUE(ExecuteScript(root, "document.body.requestPointerLock()")); |
| |
| // Root frame should have been granted pointer lock. |
| bool locked = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(root, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // 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(ExecuteScript(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()); |
| } |
| |
| 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<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| child->current_frame_host()->GetView()); |
| |
| WaitForChildFrameSurfaceReady(child->current_frame_host()); |
| |
| // Request a pointer lock on the root frame's body. |
| EXPECT_TRUE(ExecuteScript(root, "document.body.requestPointerLock()")); |
| |
| // Root frame should have been granted pointer lock. |
| bool locked = false; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(root, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // Add a mouse move wheel event listener to the root frame. |
| EXPECT_TRUE(ExecuteScript( |
| root, |
| "var x; var y; var mX; var mY; document.addEventListener('mousewheel', " |
| "function(e) {x = e.x; y = e.y; dX = e.deltaX; dY = e.deltaY;});")); |
| MainThreadFrameObserver root_observer(root_view->GetRenderWidgetHost()); |
| root_observer.Wait(); |
| |
| blink::WebMouseWheelEvent wheel_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| wheel_event.SetPositionInWidget(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(); |
| |
| if (root_view->wheel_scroll_latching_enabled()) { |
| // When wheel scroll latching is enabled 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(); |
| } |
| |
| int x, y, deltaX, deltaY; |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(x);", &x)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(y);", &y)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(dX);", &deltaX)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| root, "window.domAutomationController.send(dY);", &deltaY)); |
| EXPECT_EQ(10, x); |
| EXPECT_EQ(11, y); |
| EXPECT_EQ(12, deltaX); |
| EXPECT_EQ(13, deltaY); |
| |
| // Release pointer lock on root frame. |
| EXPECT_TRUE(ExecuteScript(root, "document.exitPointerLock()")); |
| |
| // Request a pointer lock on the child frame's body. |
| EXPECT_TRUE(ExecuteScript(child, "document.body.requestPointerLock()")); |
| |
| // Child frame should have been granted pointer lock. |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(child, |
| "window.domAutomationController.send(" |
| "document.pointerLockElement == " |
| "document.body);", |
| &locked)); |
| EXPECT_TRUE(locked); |
| |
| // Add a mouse move event listener to the child frame. |
| EXPECT_TRUE(ExecuteScript( |
| child, |
| "var x; var y; var mX; var mY; 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.delta_x = -16; |
| wheel_event.delta_y = -17; |
| if (root_view->wheel_scroll_latching_enabled()) |
| 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(); |
| |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(x);", &x)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(y);", &y)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(dX);", &deltaX)); |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| child, "window.domAutomationController.send(dY);", &deltaY)); |
| EXPECT_EQ(14, x); |
| EXPECT_EQ(15, y); |
| EXPECT_EQ(16, deltaX); |
| EXPECT_EQ(17, deltaY); |
| } |
| |
| } // namespace content |