Commit 32c154ad authored by scheib@chromium.org's avatar scheib@chromium.org

Pointer Lock: Implement pointer interface

https://bugs.webkit.org/show_bug.cgi?id=75762

Source/WebCore:

Reviewed by Julien Chaffraix.

Implement the navigator.pointer interface via a new
PointerLockController class, as per
http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html.

The implementation is being made in steps, the feature is still behind
compile-time and run-time flags, 'webkit' prefixed, and not yet enabled
in any browser. (Chromium has a developer flag required.) Follow-up
work will include handling DOM elements being removed, making all
callbacks asynchronous, iframe permissions (similar to Full Screen),
etc.

PointerLockController maintains state of which Element is the current
lock target and the success and failure callbacks. ChromeClient has
methods added to expose the required state change requests.

Tests: pointer-lock/lock-already-locked.html
       pointer-lock/lock-fail-responses.html
       pointer-lock/mouse-event-delivery.html
       pointer-lock/pointerlocklost-event.html

* WebCore.gypi:
* dom/EventNames.h:
* page/ChromeClient.h:
(WebCore::ChromeClient::requestPointerLock):
(WebCore::ChromeClient::requestPointerUnlock):
(WebCore::ChromeClient::isPointerLocked):
* page/Navigator.cpp:
(WebCore::Navigator::webkitPointer):
* page/Page.cpp:
(WebCore::Page::Page):
* page/Page.h:
(WebCore::Page::pointerLockController):
* page/PointerLock.cpp:
(WebCore::PointerLock::PointerLock):
(WebCore::PointerLock::create):
(WebCore::PointerLock::lock):
(WebCore::PointerLock::unlock):
(WebCore::PointerLock::isLocked):
* page/PointerLock.h:
(WebCore::PointerLock::create):
* page/PointerLockController.cpp: Added.
(WebCore::PointerLockController::PointerLockController):
(WebCore::PointerLockController::requestPointerLock):
(WebCore::PointerLockController::requestPointerUnlock):
(WebCore::PointerLockController::isLocked):
(WebCore::PointerLockController::didAcquirePointerLock):
(WebCore::PointerLockController::didNotAcquirePointerLock):
(WebCore::PointerLockController::didLosePointerLock):
(WebCore::PointerLockController::dispatchLockedMouseEvent):
* page/PointerLockController.h: Copied from Source/WebCore/page/PointerLock.h.

Source/WebKit/chromium:

Add calls to the PointerLockController added in this patch to WebCore.

* src/WebViewImpl.cpp:
(WebKit::WebViewImpl::close):
(WebKit::WebViewImpl::didAcquirePointerLock):
(WebKit::WebViewImpl::didNotAcquirePointerLock):
(WebKit::WebViewImpl::didLosePointerLock):
(WebKit::WebViewImpl::pointerLockMouseEvent):

LayoutTests:

Reviewed by Julien Chaffraix.

* pointer-lock/lock-already-locked-expected.txt: Added.
* pointer-lock/lock-already-locked.html: Added.
* pointer-lock/lock-fail-responses-expected.txt: Added.
* pointer-lock/lock-fail-responses.html: Added.
* pointer-lock/mouse-event-delivery-expected.txt: Added.
* pointer-lock/mouse-event-delivery.html: Added.
* pointer-lock/pointerlocklost-event-expected.txt: Added.
* pointer-lock/pointerlocklost-event.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@106134 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 875f628d
2012-01-24 Vincent Scheib <scheib@chromium.org>
Pointer Lock: Implement pointer interface
https://bugs.webkit.org/show_bug.cgi?id=75762
Reviewed by Julien Chaffraix.
* pointer-lock/lock-already-locked-expected.txt: Added.
* pointer-lock/lock-already-locked.html: Added.
* pointer-lock/lock-fail-responses-expected.txt: Added.
* pointer-lock/lock-fail-responses.html: Added.
* pointer-lock/mouse-event-delivery-expected.txt: Added.
* pointer-lock/mouse-event-delivery.html: Added.
* pointer-lock/pointerlocklost-event-expected.txt: Added.
* pointer-lock/pointerlocklost-event.html: Added.
2012-01-27 Levi Weintraub <leviw@chromium.org>
Layout Test fast/dom/navigator-detached-no-crash.html is failing
Test calling lock when already in a locked state.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS navigator.webkitPointer.isLocked is false
PASS Lock target1 succeeded.
PASS navigator.webkitPointer.isLocked is true
PASS Lock target1 succeeded again.
PASS navigator.webkitPointer.isLocked is true
PASS expectTarget1Unlock is true
PASS Lock target2 succeeded.
PASS navigator.webkitPointer.isLocked is true
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
</head>
<body>
<div>
<div id="target1"></div>
<div id="target2"></div>
</div>
<script>
description("Test calling lock when already in a locked state.")
window.jsTestIsAsync = true;
targetdiv1 = document.getElementById("target1");
targetdiv2 = document.getElementById("target2");
var expectTarget1Unlock = false;
targetdiv1.addEventListener("webkitpointerlocklost",
function () { shouldBe("expectTarget1Unlock", "true"); });
targetdiv2.addEventListener("webkitpointerlocklost",
function () { testFailed("Unexpected targetdiv2 pointerlocklost."); });
currentStep = 0;
function doNextStep() {
todo[currentStep++]();
}
todo = [
function () {
shouldBe("navigator.webkitPointer.isLocked", "false");
navigator.webkitPointer.lock(targetdiv1,
function () {
testPassed("Lock target1 succeeded.");
shouldBe("navigator.webkitPointer.isLocked", "true");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
navigator.webkitPointer.lock(targetdiv1,
function () {
testPassed("Lock target1 succeeded again.");
shouldBe("navigator.webkitPointer.isLocked", "true");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
expectTarget1Unlock = true;
navigator.webkitPointer.lock(targetdiv2,
function () {
testPassed("Lock target2 succeeded.");
shouldBe("navigator.webkitPointer.isLocked", "true");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
finishJSTest
];
doNextStep();
</script>
<script src="../fast/js/resources/js-test-post.js"></script>
</body>
</html>
Test asynchronous results of calling lock.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS window.layoutTestController is defined.
PASS Lock failed (after we called layoutTestController.setPointerLockWillFailSynchronously)
PASS Lock failed (after we called layoutTestController.setPointerLockWillFailAsynchronously)
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Test asynchronous results of calling lock.")
window.jsTestIsAsync = true;
shouldBeDefined("window.layoutTestController");
currentStep = 0;
function doNextStep() {
todo[currentStep++]();
}
todo = [
function () {
layoutTestController.setPointerLockWillFailSynchronously();
navigator.webkitPointer.lock(document.body,
function () {
testFailed("Lock succeeded unexpectedly.");
finishJSTest();
},
function () {
testPassed("Lock failed (after we called layoutTestController.setPointerLockWillFailSynchronously)");
doNextStep();
});
},
function () {
layoutTestController.setPointerLockWillFailAsynchronously();
navigator.webkitPointer.lock(document.body,
function () {
testFailed("Lock succeeded unexpectedly.");
finishJSTest();
},
function () {
testPassed("Lock failed (after we called layoutTestController.setPointerLockWillFailAsynchronously)");
doNextStep();
});
},
finishJSTest
];
doNextStep();
</script>
<script src="../fast/js/resources/js-test-post.js"></script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
......
Test mouse events are routed to lock target.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS window.eventSender is defined.
PASS Lock target1 succeeded.
With a lock in place send a click.
PASS event type: mousedown, target: target1, received on: target1
PASS event type: mousedown, target: target1, received on: body
PASS Lock target2 succeeded.
With a lock in place send a click.
PASS event type: mousedown, target: target2, received on: target2
PASS event type: mousedown, target: target2, received on: body
Ensure double clicks work as expected, sending 4 clicks.
Creating a click and dblclick.
PASS event type: mousedown, target: target2, received on: target2
PASS event type: mouseup, target: target2, received on: target2
PASS event type: click, target: target2, received on: target2
PASS event type: mousedown, target: target2, received on: target2
PASS event type: mouseup, target: target2, received on: target2
PASS event type: click, target: target2, received on: target2
PASS event type: dblclick, target: target2, received on: target2
Ensuring no false dblclicks if we continue.
PASS event type: mousedown, target: target2, received on: target2
PASS event type: mouseup, target: target2, received on: target2
PASS event type: click, target: target2, received on: target2
PASS event type: mousedown, target: target2, received on: target2
PASS event type: mouseup, target: target2, received on: target2
PASS event type: click, target: target2, received on: target2
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
</head>
<body id="body">
<div>
<div id="target1"></div>
<div id="target2"></div>
</div>
<script>
description("Test mouse events are routed to lock target.")
window.jsTestIsAsync = true;
shouldBeDefined("window.eventSender");
targetdiv1 = document.getElementById("target1");
targetdiv2 = document.getElementById("target2");
function eventNotExpected(e) {
testFailed("Unexpected event " + e.type + " on " + e.currentTarget.id);
finishJSTest();
}
function eventExpected(e) {
testPassed("event type: " + e.type + ", target: " + e.target.id + ", received on: " + e.currentTarget.id);
}
currentStep = 0;
function doNextStep() {
todo[currentStep++]();
}
todo = [
function () {
navigator.webkitPointer.lock(targetdiv1,
function () {
testPassed("Lock target1 succeeded.");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
debug("With a lock in place send a click.")
targetdiv1.onmousedown = eventExpected;
targetdiv2.onmousedown = eventNotExpected;
document.body.onmousedown = eventExpected;
window.eventSender.leapForward(1000);
window.eventSender.mouseDown();
window.eventSender.mouseUp();
targetdiv1.onmousedown = undefined;
targetdiv2.onmousedown = undefined;
document.body.onmousedown = undefined;
doNextStep();
},
function () {
navigator.webkitPointer.lock(targetdiv2,
function () {
testPassed("Lock target2 succeeded.");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
debug("With a lock in place send a click.")
targetdiv1.onmousedown = eventNotExpected;
targetdiv2.onmousedown = eventExpected;
document.body.onmousedown = eventExpected;
window.eventSender.leapForward(1000);
window.eventSender.mouseDown();
window.eventSender.mouseUp();
targetdiv1.onmousedown = undefined;
targetdiv2.onmousedown = undefined;
document.body.onmousedown = undefined;
doNextStep();
},
function () {
debug("Ensure double clicks work as expected, sending 4 clicks.")
targetdiv2.onmousedown = eventExpected;
targetdiv2.onmouseup = eventExpected;
targetdiv2.onclick = eventExpected;
targetdiv2.ondblclick = eventExpected;
debug("Creating a click and dblclick.");
window.eventSender.leapForward(1000);
window.eventSender.mouseDown();
window.eventSender.mouseUp();
window.eventSender.mouseDown();
window.eventSender.mouseUp();
debug("Ensuring no false dblclicks if we continue.");
window.eventSender.mouseDown();
window.eventSender.mouseUp();
window.eventSender.mouseDown();
window.eventSender.mouseUp();
targetdiv2.onmousedown = undefined;
targetdiv2.onmouseup = undefined;
targetdiv2.onclick = undefined;
targetdiv2.ondblclick = undefined;
doNextStep();
},
finishJSTest
];
doNextStep();
</script>
<script src="../fast/js/resources/js-test-post.js"></script>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
......
Test that pointerlocklost event is dispatched.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS window.layoutTestController is defined.
PASS navigator.webkitPointer.isLocked is true
JavaScript initiated unlock.
PASS webkitpointerlocklost handler call 0
PASS webkitpointerlocklost handler call 1
PASS All expected webkitpointerlocklost events received.
PASS navigator.webkitPointer.isLocked is false
PASS navigator.webkitPointer.isLocked is true
Host initiated unlock.
PASS webkitpointerlocklost handler call 0
PASS webkitpointerlocklost handler call 1
PASS All expected webkitpointerlocklost events received.
PASS navigator.webkitPointer.isLocked is false
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML>
<html>
<head>
<script src="../fast/js/resources/js-test-pre.js"></script>
</head>
<body>
<div>
<div id="target"></div>
</div>
<script>
description("Test that pointerlocklost event is dispatched.")
window.jsTestIsAsync = true;
shouldBeDefined("window.layoutTestController");
targetdiv = document.getElementById("target");
document.addEventListener("webkitpointerlocklost", locklostHandler);
targetdiv.addEventListener("webkitpointerlocklost", locklostHandler);
locklosthandlers = 2;
locklostreceipts = 0;
function locklostHandler() {
testPassed("webkitpointerlocklost handler call " + locklostreceipts);
locklostreceipts++;
if (locklostreceipts == locklosthandlers) {
testPassed("All expected webkitpointerlocklost events received.");
doNextStep();
}
}
currentStep = 0;
function doNextStep() {
todo[currentStep++]();
}
todo = [
function () {
navigator.webkitPointer.lock(targetdiv,
function () {
shouldBe("navigator.webkitPointer.isLocked", "true");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
debug("JavaScript initiated unlock.")
locklostreceipts = 0;
navigator.webkitPointer.unlock();
},
function () {
// locklostHandler will catch unlocks and call doNextStep to bring us here.
shouldBe("navigator.webkitPointer.isLocked", "false");
doNextStep();
},
function () {
navigator.webkitPointer.lock(targetdiv,
function () {
shouldBe("navigator.webkitPointer.isLocked", "true");
doNextStep();
},
function () {
testFailed("Lock failed.");
finishJSTest();
});
},
function () {
debug("Host initiated unlock.")
locklostreceipts = 0;
layoutTestController.didLosePointerLock();
},
function () {
// locklostHandler will catch unlocks and call doNextStep to bring us here.
shouldBe("navigator.webkitPointer.isLocked", "false");
doNextStep();
},
finishJSTest
];
doNextStep();
</script>
<script src="../fast/js/resources/js-test-post.js"></script>
</body>
</html>
2012-01-24 Vincent Scheib <scheib@chromium.org>
Pointer Lock: Implement pointer interface
https://bugs.webkit.org/show_bug.cgi?id=75762
Reviewed by Julien Chaffraix.
Implement the navigator.pointer interface via a new
PointerLockController class, as per
http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html.
The implementation is being made in steps, the feature is still behind
compile-time and run-time flags, 'webkit' prefixed, and not yet enabled
in any browser. (Chromium has a developer flag required.) Follow-up
work will include handling DOM elements being removed, making all
callbacks asynchronous, iframe permissions (similar to Full Screen),
etc.
PointerLockController maintains state of which Element is the current
lock target and the success and failure callbacks. ChromeClient has
methods added to expose the required state change requests.
Tests: pointer-lock/lock-already-locked.html
pointer-lock/lock-fail-responses.html
pointer-lock/mouse-event-delivery.html
pointer-lock/pointerlocklost-event.html
* WebCore.gypi:
* dom/EventNames.h:
* page/ChromeClient.h:
(WebCore::ChromeClient::requestPointerLock):
(WebCore::ChromeClient::requestPointerUnlock):
(WebCore::ChromeClient::isPointerLocked):
* page/Navigator.cpp:
(WebCore::Navigator::webkitPointer):
* page/Page.cpp:
(WebCore::Page::Page):
* page/Page.h:
(WebCore::Page::pointerLockController):
* page/PointerLock.cpp:
(WebCore::PointerLock::PointerLock):
(WebCore::PointerLock::create):
(WebCore::PointerLock::lock):
(WebCore::PointerLock::unlock):
(WebCore::PointerLock::isLocked):
* page/PointerLock.h:
(WebCore::PointerLock::create):
* page/PointerLockController.cpp: Added.
(WebCore::PointerLockController::PointerLockController):
(WebCore::PointerLockController::requestPointerLock):
(WebCore::PointerLockController::requestPointerUnlock):
(WebCore::PointerLockController::isLocked):
(WebCore::PointerLockController::didAcquirePointerLock):
(WebCore::PointerLockController::didNotAcquirePointerLock):
(WebCore::PointerLockController::didLosePointerLock):
(WebCore::PointerLockController::dispatchLockedMouseEvent):
* page/PointerLockController.h: Copied from Source/WebCore/page/PointerLock.h.
2012-01-27 Abhishek Arya <inferno@chromium.org>
Crash in DocumentLoader::detachFromFrame.
......@@ -2647,6 +2647,8 @@
'page/PerformanceTiming.h',
'page/PointerLock.cpp',
'page/PointerLock.h',
'page/PointerLockController.cpp',
'page/PointerLockController.h',
'page/PrintContext.cpp',
'page/Screen.cpp',
'page/Screen.h',
......
......@@ -197,6 +197,8 @@ namespace WebCore {
\
macro(show) \
\
macro(webkitpointerlocklost) \
\
// end of DOM_EVENT_NAMES_FOR_EACH
......
......@@ -328,6 +328,12 @@ namespace WebCore {
virtual bool isSVGImageChromeClient() const { return false; }
#if ENABLE(POINTER_LOCK)
virtual bool requestPointerLock() { return false; }
virtual void requestPointerUnlock() { }
virtual bool isPointerLocked() { return false; }
#endif
protected:
virtual ~ChromeClient() { }
};
......
......@@ -161,8 +161,8 @@ Geolocation* Navigator::geolocation() const
#if ENABLE(POINTER_LOCK)
PointerLock* Navigator::webkitPointer() const
{
if (!m_pointer)
m_pointer = PointerLock::create();
if (!m_pointer && m_frame && m_frame->page())
m_pointer = PointerLock::create(m_frame);
return m_pointer.get();
}
#endif
......
......@@ -59,6 +59,7 @@
#include "PluginData.h"
#include "PluginView.h"
#include "PluginViewBase.h"
#include "PointerLockController.h"
#include "ProgressTracker.h"
#include "RenderTheme.h"
#include "RenderView.h"
......@@ -146,6 +147,9 @@ Page::Page(PageClients& pageClients)
#if ENABLE(NOTIFICATIONS)
, m_notificationController(NotificationController::create(this, pageClients.notificationClient))
#endif
#if ENABLE(POINTER_LOCK)
, m_pointerLockController(PointerLockController::create(this))
#endif
#if ENABLE(INPUT_SPEECH)
, m_speechInputClient(pageClients.speechInputClient)
#endif
......
......@@ -76,6 +76,7 @@ namespace WebCore {
class NotificationPresenter;
class PageGroup;
class PluginData;
class PointerLockController;
class ProgressTracker;
class Range;
class RenderTheme;
......@@ -190,6 +191,9 @@ namespace WebCore {
#if ENABLE(NOTIFICATIONS)
NotificationController* notificationController() const { return m_notificationController.get(); }
#endif
#if ENABLE(POINTER_LOCK)
PointerLockController* pointerLockController() const { return m_pointerLockController.get(); }
#endif
#if ENABLE(INPUT_SPEECH)
SpeechInput* speechInput();
#endif
......@@ -386,6 +390,9 @@ namespace WebCore {
#if ENABLE(NOTIFICATIONS)
OwnPtr<NotificationController> m_notificationController;
#endif
#if ENABLE(POINTER_LOCK)
OwnPtr<PointerLockController> m_pointerLockController;
#endif
#if ENABLE(INPUT_SPEECH)
SpeechInputClient* m_speechInputClient;
OwnPtr<SpeechInput> m_speechInput;
......
......@@ -25,32 +25,48 @@
#include "config.h"
#include "PointerLock.h"
#include "Frame.h"
#include "Page.h"
#include "PointerLockController.h"
#if ENABLE(POINTER_LOCK)
namespace WebCore {
PointerLock::PointerLock()
PointerLock::PointerLock(Frame* frame)
: DOMWindowProperty(frame)
, m_controller(0)
{
ASSERT(m_frame);
m_controller = frame->page()->pointerLockController();
}
PointerLock::~PointerLock()
{
ASSERT(!m_controller);
}
void PointerLock::disconnectFrame()
{
DOMWindowProperty::disconnectFrame();
m_controller = 0;
}
void PointerLock::lock(Element* target, PassRefPtr<VoidCallback> successCallback, PassRefPtr<VoidCallback> failureCallback)
{
// FIXME: Implement
if (m_controller)
m_controller->requestPointerLock(target, successCallback, failureCallback);
}
void PointerLock::unlock()
{
// FIXME: Implement
if (m_controller)
m_controller->requestPointerUnlock();
}
bool PointerLock::isLocked()
{
// FIXME: Implement
return false;
return m_controller && m_controller->isLocked();
}
}
......
......@@ -27,6 +27,7 @@
#if ENABLE(POINTER_LOCK)
#include "DOMWindowProperty.h"
#include "VoidCallback.h"
#include <wtf/PassRefPtr.h>
#include <wtf/RefCounted.h>
......@@ -34,19 +35,26 @@
namespace WebCore {
class Element;
class Frame;
class PointerLockController;
class PointerLock : public RefCounted<PointerLock> {
class PointerLock : public RefCounted<PointerLock>, public DOMWindowProperty {
public:
static PassRefPtr<PointerLock> create() { return adoptRef(new PointerLock()); }
static PassRefPtr<PointerLock> create(Frame* frame) { return adoptRef(new PointerLock(frame)); }