Bug 1284788 - Allow change target of pointer lock when the pointer has been locked in the document. r=smaug
MozReview-Commit-ID: HiPkCPrQQr0
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12364,16 +12364,49 @@ GetPointerLockError(Element* aElement, E
if (!top->GetExtantDoc()->HasFocus(rv)) {
return "PointerLockDeniedNotFocused";
}
}
return nullptr;
}
+static void
+ChangePointerLockedElement(Element* aElement, nsIDocument* aDocument,
+ Element* aPointerLockedElement)
+{
+ // aDocument here is not really necessary, as it is the uncomposed
+ // document of both aElement and aPointerLockedElement as far as one
+ // is not nullptr, and they wouldn't both be nullptr in any case.
+ // But since the caller of this function should have known what the
+ // document is, we just don't try to figure out what it should be.
+ MOZ_ASSERT(aDocument);
+ MOZ_ASSERT(aElement != aPointerLockedElement);
+ if (aPointerLockedElement) {
+ MOZ_ASSERT(aPointerLockedElement->GetUncomposedDoc() == aDocument);
+ aPointerLockedElement->ClearPointerLock();
+ }
+ if (aElement) {
+ MOZ_ASSERT(aElement->GetUncomposedDoc() == aDocument);
+ aElement->SetPointerLock();
+ EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
+ EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument);
+ NS_ASSERTION(EventStateManager::sPointerLockedElement &&
+ EventStateManager::sPointerLockedDoc,
+ "aElement and this should support weak references!");
+ } else {
+ EventStateManager::sPointerLockedElement = nullptr;
+ EventStateManager::sPointerLockedDoc = nullptr;
+ }
+ // Retarget all events to aElement via capture or
+ // stop retargeting if aElement is nullptr.
+ nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
+ DispatchPointerLockChange(aDocument);
+}
+
NS_IMETHODIMP
PointerLockRequest::Run()
{
nsCOMPtr<Element> e = do_QueryReferent(mElement);
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
nsDocument* d = static_cast<nsDocument*>(doc.get());
const char* error = nullptr;
if (!e || !d || !e->GetUncomposedDoc()) {
@@ -12385,42 +12418,40 @@ PointerLockRequest::Run()
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(EventStateManager::sPointerLockedElement);
if (e == pointerLockedElement) {
DispatchPointerLockChange(d);
return NS_OK;
}
// Note, we must bypass focus change, so pass true as the last parameter!
error = GetPointerLockError(e, pointerLockedElement, true);
+ // Another element in the same document is requesting pointer lock,
+ // just grant it without user input check.
+ if (!error && pointerLockedElement) {
+ ChangePointerLockedElement(e, d, pointerLockedElement);
+ return NS_OK;
+ }
}
// If it is neither user input initiated, nor requested in fullscreen,
// it should be rejected.
if (!error && !mUserInputOrChromeCaller && !doc->GetFullscreenElement()) {
error = "PointerLockDeniedNotInputDriven";
}
if (!error && !d->SetPointerLock(e, NS_STYLE_CURSOR_NONE)) {
error = "PointerLockDeniedFailedToLock";
}
if (error) {
DispatchPointerLockError(d, error);
return NS_OK;
}
- e->SetPointerLock();
- EventStateManager::sPointerLockedElement = do_GetWeakReference(e);
- EventStateManager::sPointerLockedDoc = do_GetWeakReference(doc);
- NS_ASSERTION(EventStateManager::sPointerLockedElement &&
- EventStateManager::sPointerLockedDoc,
- "aElement and this should support weak references!");
-
+ ChangePointerLockedElement(e, d, nullptr);
nsContentUtils::DispatchEventOnlyToChrome(
doc, ToSupports(e), NS_LITERAL_STRING("MozDOMPointerLock:Entered"),
/* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
-
- DispatchPointerLockChange(d);
return NS_OK;
}
void
nsDocument::RequestPointerLock(Element* aElement)
{
NS_ASSERTION(aElement,
"Must pass non-null element to nsDocument::RequestPointerLock");
@@ -12502,29 +12533,22 @@ nsDocument::UnlockPointer(nsIDocument* a
}
nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get());
if (!doc->SetPointerLock(nullptr, NS_STYLE_CURSOR_AUTO)) {
return;
}
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(EventStateManager::sPointerLockedElement);
- if (pointerLockedElement) {
- pointerLockedElement->ClearPointerLock();
- }
-
- EventStateManager::sPointerLockedElement = nullptr;
- EventStateManager::sPointerLockedDoc = nullptr;
+ ChangePointerLockedElement(nullptr, doc, pointerLockedElement);
nsContentUtils::DispatchEventOnlyToChrome(
doc, ToSupports(pointerLockedElement),
NS_LITERAL_STRING("MozDOMPointerLock:Exited"),
/* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
-
- DispatchPointerLockChange(pointerLockedDoc);
}
void
nsIDocument::UnlockPointer(nsIDocument* aDoc)
{
nsDocument::UnlockPointer(aDoc);
}
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -4341,19 +4341,16 @@ EventStateManager::SetPointerLock(nsIWid
// Fire a synthetic mouse move to ensure event state is updated. We first
// set the mouse to the center of the window, so that the mouse event
// doesn't report any movement.
sLastRefPoint = GetWindowClientRectCenter(aWidget);
aWidget->SynthesizeNativeMouseMove(
sLastRefPoint + aWidget->WidgetToScreenOffset(), nullptr);
- // Retarget all events to this element via capture.
- nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
-
// Suppress DnD
if (dragService) {
dragService->Suppress();
}
} else {
// Unlocking, so return pointer to the original position by firing a
// synthetic mouse event. We first reset sLastRefPoint to its
// pre-pointerlock position, so that the synthetic mouse event reports
@@ -4362,19 +4359,16 @@ EventStateManager::SetPointerLock(nsIWid
// Reset SynthCenteringPoint to invalid so that next time we start
// locking pointer, it has its initial value.
sSynthCenteringPoint = kInvalidRefPoint;
if (aWidget) {
aWidget->SynthesizeNativeMouseMove(
mPreLockPoint + aWidget->WidgetToScreenOffset(), nullptr);
}
- // Don't retarget events to this element any more.
- nsIPresShell::SetCapturingContent(nullptr, CAPTURE_POINTERLOCK);
-
// Unsuppress DnD
if (dragService) {
dragService->Unsuppress();
}
}
}
void
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_changeLockElement.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Bug 1284788</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="pointerlock_utils.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #block1, #block2, #block3 {
+ background: blue;
+ width: 50px; height: 50px;
+ margin: 10px;
+ }
+ </style>
+</head>
+<body>
+ <div id="block1"></div>
+ <div id="block2"></div>
+ <div id="block3"></div>
+ <div id="test">
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("For changing pointer lock element not in a valid user event handler");
+
+ var block1 = document.getElementById("block1");
+ var block2 = document.getElementById("block2");
+ var block3 = document.getElementById("block3");
+
+ class ClickTester {
+ constructor(target) {
+ this._target = target;
+ this._callback = null;
+ document.addEventListener("click", this);
+ }
+
+ synthesize(callback) {
+ ok(!this._callback, "No callback should have been hooked");
+ this._callback = callback;
+ synthesizeMouseAtCenter(this._target, {}, window);
+ }
+
+ handleEvent(e) {
+ ok(!!this._callback, "Should have hooked a callback");
+ var callback = this._callback;
+ this._callback = null;
+ callback(e);
+ }
+ };
+
+ var tester = new ClickTester(block3);
+ // It would be called in handler of load event in pointerlock_utils.js
+ function start() {
+ tester.synthesize(firstClick);
+ }
+
+ function firstClick(e) {
+ is(e.target, block3, "Click is triggered inside block3");
+ document.addEventListener("mozpointerlockchange", lockedPointerOnBlock1);
+ block1.mozRequestPointerLock();
+ }
+
+ function lockedPointerOnBlock1() {
+ document.removeEventListener("mozpointerlockchange", lockedPointerOnBlock1);
+ is(document.mozPointerLockElement, block1, "Pointer should be locked on #block1");
+ SimpleTest.executeSoon(() => {
+ tester.synthesize(secondClick);
+ });
+ }
+
+ function secondClick(e) {
+ is(e.target, block1, "Event should be redirected to block1");
+ // Use 2s to ensure that we never consider this as an extension of user input.
+ setTimeout(() => {
+ document.addEventListener("mozpointerlockchange", lockedPointerOnBlock2);
+ block2.mozRequestPointerLock();
+ }, 2000);
+ }
+
+ function lockedPointerOnBlock2() {
+ document.removeEventListener("mozpointerlockchange", lockedPointerOnBlock2);
+ is(document.mozPointerLockElement, block2, "Pointer should be locked on #block2");
+ SimpleTest.executeSoon(() => {
+ tester.synthesize(thirdClick);
+ });
+ }
+
+ function thirdClick(e) {
+ is(e.target, block2, "Event should be redirected to block2");
+ // Use 2s to ensure that we never consider this as an extension of user input.
+ setTimeout(() => {
+ document.addEventListener("mozpointerlockchange", lockedPointerOnBlock1Again);
+ block1.mozRequestPointerLock();
+ }, 2000);
+ }
+
+ function lockedPointerOnBlock1Again() {
+ document.removeEventListener("mozpointerlockchange", lockedPointerOnBlock1Again);
+ is(document.mozPointerLockElement, block1, "Pointer should be locked on #block1 again");
+ SimpleTest.executeSoon(() => {
+ tester.synthesize(fourthClick);
+ });
+ }
+
+ function fourthClick(e) {
+ is(e.target, block1, "Event should be redirected to block1 again");
+ document.addEventListener("mozpointerlockchange", () => SimpleTest.finish());
+ document.mozExitPointerLock();
+ }
+
+ </script>
+ </div>
+</body>
+</html>
--- a/dom/tests/mochitest/pointerlock/mochitest.ini
+++ b/dom/tests/mochitest/pointerlock/mochitest.ini
@@ -13,13 +13,14 @@ support-files =
file_movementXY.html
file_infiniteMovement.html
file_retargetMouseEvents.html
file_targetOutOfFocus.html
file_screenClientXYConst.html
file_suppressSomeMouseEvents.html
file_locksvgelement.html
file_allowPointerLockSandboxFlag.html
+ file_changeLockElement.html
iframe_differentDOM.html
[test_pointerlock-api.html]
tags = fullscreen
skip-if = buildapp == 'b2g' || toolkit == 'android' || os == 'win' # B2G - window.open focus issues using fullscreen. Win: Bug 931445
--- a/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
+++ b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
@@ -51,17 +51,18 @@ https://bugzilla.mozilla.org/show_bug.cg
"file_pointerlock-api.html",
"file_pointerlockerror.html",
"file_pointerLockPref.html",
"file_removedFromDOM.html",
"file_retargetMouseEvents.html",
"file_suppressSomeMouseEvents.html",
"file_targetOutOfFocus.html",
"file_withoutDOM.html",
- "file_allowPointerLockSandboxFlag.html"
+ "file_allowPointerLockSandboxFlag.html",
+ "file_changeLockElement.html",
];
var gDisableList = [
];
var gTestWindow = null;
var gTestIndex = 0;