Files
sousa-gecko/dom/base/PointerLockManager.cpp
T
Edgar Chen d98f16e74d Bug 1255338 - Part 2: Reset pointer position for pointer lock from parent process; r=smaug
This is gated behind a new pref: dom.pointer-lock.reset-to-center-from-parent.enabled.
The patch preserves the original behavior as much as possible when the pref is off.
There are some changes that affect both code paths, but they are minor and
should not affect the main logic.

Here are some major change when the pref is enabled:
- Pointer recentering and computation of the center point are now handled in the parent process.
- Locking native pointer is initiated from parent process as well.
- When a remote target requests pointer lock, the parent process captures the mouse
  on the top-level browser element. This is necessary because the center of the
  chrome window may not correspond to the web page (for example, when a DevTools
  panel is open and resized to be very large).
- This patch introduces an optimization that recenters the pointer only when it
  moves within a 25% boundary buffer near the edge of the window, reducing the
  number of recentering operations. Since each platform now uses a native API to
  "lock" the pointer, we do not need to worry as much about the pointer escaping
  pointer lock by moving outside the browser window at high speed.

Differential Revision: https://phabricator.services.mozilla.com/D296321
2026-06-26 17:35:07 +00:00

715 lines
24 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "PointerLockManager.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Logging.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/PointerEventHandler.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WindowContext.h"
#include "nsCOMPtr.h"
#include "nsIWidget.h"
#include "nsMenuPopupFrame.h"
#include "nsSandboxFlags.h"
mozilla::LazyLogModule gPointerLockLog("PointerLock");
#define MOZ_POINTERLOCK_LOG(...) \
MOZ_LOG(gPointerLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
namespace mozilla {
using mozilla::dom::BrowserChild;
using mozilla::dom::BrowserParent;
using mozilla::dom::BrowsingContext;
using mozilla::dom::CallerType;
using mozilla::dom::CanonicalBrowsingContext;
using mozilla::dom::Document;
using mozilla::dom::Element;
using mozilla::dom::PointerLockOptions;
using mozilla::dom::Promise;
using mozilla::dom::WindowContext;
// Reference to the pointer locked element.
constinit static nsWeakPtr sLockedElement;
// Reference to the document which requested pointer lock.
constinit static nsWeakPtr sLockedDoc;
// Reference to the BrowserParent requested pointer lock.
static BrowserParent* sLockedRemoteTarget = nullptr;
/* static */
bool PointerLockManager::sIsLocked = false;
/* static */
bool PointerLockManager::sIsLockUnadjustedMovement = false;
// Map a Gecko-internal pointer-lock error string to the DOMException defined in
// the spec, https://w3c.github.io/pointerlock/#dom-element-requestpointerlock.
static void RejectPromiseForError(Promise* aPromise, const char* aError) {
MOZ_ASSERT(aPromise);
MOZ_ASSERT(aError);
if (!strcmp(aError, "PointerLockDeniedDisabled")) {
aPromise->MaybeRejectWithNotSupportedError("Pointer Lock API is disabled.");
return;
}
if (!strcmp(aError, "PointerLockDeniedNotInDocument")) {
aPromise->MaybeRejectWithInvalidStateError(
"The requesting element is not in a document.");
return;
}
if (!strcmp(aError, "PointerLockDeniedSandboxed")) {
aPromise->MaybeRejectWithSecurityError(
"Pointer Lock API is restricted via sandbox.");
return;
}
// Other browsers allow the hidden document to lock the pointer, but there is
// a spec issue for that, https://github.com/w3c/pointerlock/issues/93.
if (!strcmp(aError, "PointerLockDeniedHidden")) {
aPromise->MaybeRejectWithWrongDocumentError("The document is not visible.");
return;
}
if (!strcmp(aError, "PointerLockDeniedNotFocused")) {
aPromise->MaybeRejectWithWrongDocumentError("The document is not focused.");
return;
}
if (!strcmp(aError, "PointerLockDeniedFailedToLock")) {
aPromise->MaybeRejectWithNotSupportedError(
"The browser failed to lock the pointer.");
return;
}
if (!strcmp(aError, "PointerLockDeniedInUse")) {
aPromise->MaybeRejectWithInvalidStateError(
"The pointer is currently locked by a different document.");
return;
}
if (!strcmp(aError, "PointerLockDeniedNotInputDriven")) {
aPromise->MaybeRejectWithNotAllowedError(
"Element.requestPointerLock() was not called from inside a short "
"running user-generated event handler, and the document is not in full "
"screen.");
return;
}
if (!strcmp(aError, "PointerLockDeniedMovedDocument")) {
aPromise->MaybeRejectWithInvalidStateError(
"The requesting element has moved to a different document");
return;
}
MOZ_ASSERT_UNREACHABLE("Unknown pointer lock error");
aPromise->MaybeRejectWithInvalidStateError("Unknown error.");
}
/* static */
already_AddRefed<dom::Element> PointerLockManager::GetLockedElement() {
nsCOMPtr<Element> element = do_QueryReferent(sLockedElement);
return element.forget();
}
/* static */
already_AddRefed<dom::Document> PointerLockManager::GetLockedDocument() {
nsCOMPtr<Document> document = do_QueryReferent(sLockedDoc);
return document.forget();
}
/* static */
BrowserParent* PointerLockManager::GetLockedRemoteTarget() {
MOZ_ASSERT(XRE_IsParentProcess());
return sLockedRemoteTarget;
}
static void DispatchPointerLockChange(Document* aTarget) {
if (!aTarget) {
return;
}
MOZ_POINTERLOCK_LOG("Dispatch pointerlockchange event [document=0x%p]",
aTarget);
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(aTarget, u"pointerlockchange"_ns,
CanBubble::eYes, ChromeOnlyDispatch::eNo);
asyncDispatcher->PostDOMEvent();
}
static void DispatchPointerLockError(Document* aTarget, const char* aMessage) {
MOZ_ASSERT(aMessage);
if (!aTarget) {
return;
}
MOZ_POINTERLOCK_LOG(
"Dispatch pointerlockerror event [document=0x%p, message=%s]", aTarget,
aMessage);
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(aTarget, u"pointerlockerror"_ns, CanBubble::eYes,
ChromeOnlyDispatch::eNo);
asyncDispatcher->PostDOMEvent();
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
aTarget, PropertiesFile::DOM_PROPERTIES,
aMessage);
}
// Combined error path: fires pointerlockerror for backwards compatibility AND
// rejects the spec-mandated Promise with the appropriate DOMException.
static void FailWith(Document* aTarget, Promise* aPromise, const char* aError) {
DispatchPointerLockError(aTarget, aError);
RejectPromiseForError(aPromise, aError);
}
static bool IsPopupOpened() {
// Check if any popup is open.
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (!pm) {
return false;
}
nsTArray<nsMenuPopupFrame*> popups;
pm->GetVisiblePopups(popups, true);
for (nsMenuPopupFrame* popup : popups) {
if (popup->GetPopupType() != widget::PopupType::Tooltip) {
return true;
}
}
return false;
}
static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock,
bool aNoFocusCheck = false) {
// Check if pointer lock pref is enabled
if (!StaticPrefs::full_screen_api_pointer_lock_enabled()) {
return "PointerLockDeniedDisabled";
}
nsCOMPtr<Document> ownerDoc = aElement->OwnerDoc();
if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
return "PointerLockDeniedInUse";
}
if (!aElement->IsInComposedDoc()) {
return "PointerLockDeniedNotInDocument";
}
if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) {
return "PointerLockDeniedSandboxed";
}
// Check if the element is in a document with a docshell.
if (!ownerDoc->GetContainer()) {
return "PointerLockDeniedHidden";
}
nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow();
if (!ownerWindow) {
return "PointerLockDeniedHidden";
}
nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow();
if (!ownerInnerWindow) {
return "PointerLockDeniedHidden";
}
if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
return "PointerLockDeniedHidden";
}
BrowsingContext* bc = ownerDoc->GetBrowsingContext();
BrowsingContext* topBC = bc ? bc->Top() : nullptr;
WindowContext* topWC = ownerDoc->GetTopLevelWindowContext();
if (!topBC || !topBC->IsActive() || !topWC ||
topWC != topBC->GetCurrentWindowContext()) {
return "PointerLockDeniedHidden";
}
if (!aNoFocusCheck) {
if (!IsInActiveTab(ownerDoc)) {
return "PointerLockDeniedNotFocused";
}
}
if (IsPopupOpened()) {
return "PointerLockDeniedFailedToLock";
}
return nullptr;
}
/* static */
void PointerLockManager::RequestLock(Element* aElement,
const PointerLockOptions& aOptions,
CallerType aCallerType,
Promise* aPromise) {
MOZ_ASSERT(aPromise);
NS_ASSERTION(aElement,
"Must pass non-null element to PointerLockManager::RequestLock");
RefPtr<Document> doc = aElement->OwnerDoc();
nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
MOZ_POINTERLOCK_LOG(
"Request lock on element 0x%p [document=0x%p, "
"unadjustedMovement=%s]",
aElement, doc.get(), aOptions.mUnadjustedMovement ? "true" : "false");
// XXX: https://bugzilla.mozilla.org/show_bug.cgi?id=2037874.
// Spec does this check in queued task instead, see step 6-2 of
// https://w3c.github.io/pointerlock/#dom-element-requestpointerlock.
if (aElement == pointerLockedElement &&
sIsLockUnadjustedMovement == aOptions.mUnadjustedMovement) {
DispatchPointerLockChange(doc);
aPromise->MaybeResolveWithUndefined();
return;
}
if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
FailWith(doc, aPromise, msg);
return;
}
bool userInputOrSystemCaller =
doc->HasValidTransientUserGestureActivation() ||
aCallerType == CallerType::System;
nsCOMPtr<nsIRunnable> request =
new PointerLockRequest(aElement, userInputOrSystemCaller,
aOptions.mUnadjustedMovement, aPromise);
doc->Dispatch(request.forget());
}
/* static */
void PointerLockManager::Unlock(const char* aReason, Document* aDoc) {
if (sLockedRemoteTarget) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!sIsLocked);
if (aDoc) {
CanonicalBrowsingContext* lockedBc =
sLockedRemoteTarget->GetBrowsingContext();
if (lockedBc &&
lockedBc->TopCrossChromeBoundary()->GetExtantDocument() != aDoc) {
return;
}
}
MOZ_POINTERLOCK_LOG(
"Unlock document 0x%p [sLockedRemoteTarget=0x%p, reason=%s]", aDoc,
sLockedRemoteTarget, aReason);
(void)sLockedRemoteTarget->SendReleasePointerLock();
PointerLockManager::ReleaseLockedRemoteTarget(sLockedRemoteTarget);
return;
}
if (!sIsLocked) {
return;
}
nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
MOZ_POINTERLOCK_LOG("Unlock document 0x%p [LockedDocument=0x%p, reason=%s]",
aDoc, pointerLockedDoc.get(), aReason);
if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
return;
}
if (!SetPointerLock(nullptr, pointerLockedDoc, StyleCursorKind::Auto,
/* aUnadjustedMovement */ false)) {
return;
}
nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
ChangePointerLockedElement(nullptr, pointerLockedDoc, pointerLockedElement);
if (BrowserChild* browserChild =
BrowserChild::GetFrom(pointerLockedDoc->GetDocShell())) {
browserChild->SendReleasePointerLock();
}
AsyncEventDispatcher::RunDOMEventWhenSafe(
*pointerLockedElement, u"MozDOMPointerLock:Exited"_ns, CanBubble::eYes,
ChromeOnlyDispatch::eYes);
}
/* static */
void PointerLockManager::ChangePointerLockedElement(
Element* aElement, Document* 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);
MOZ_POINTERLOCK_LOG("Change locked element from 0x%p to 0x%p [document=0x%p]",
aPointerLockedElement, aElement, aDocument);
if (aPointerLockedElement) {
MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument);
aPointerLockedElement->ClearPointerLock();
}
if (aElement) {
MOZ_ASSERT(aElement->GetComposedDoc() == aDocument);
aElement->SetPointerLock();
sLockedElement = do_GetWeakReference(aElement);
sLockedDoc = do_GetWeakReference(aDocument);
NS_ASSERTION(sLockedElement && sLockedDoc,
"aElement and this should support weak references!");
} else {
sLockedElement = nullptr;
sLockedDoc = nullptr;
}
// Retarget all events to aElement via capture or
// stop retargeting if aElement is nullptr.
PresShell::SetCapturingContent(aElement, CaptureFlags::PointerLock);
DispatchPointerLockChange(aDocument);
}
/* static */
bool PointerLockManager::StartSetPointerLock(Element* aElement,
Document* aDocument,
bool aUnadjustedMovement) {
if (!SetPointerLock(aElement, aDocument, StyleCursorKind::None,
aUnadjustedMovement)) {
DispatchPointerLockError(aDocument, "PointerLockDeniedFailedToLock");
return false;
}
ChangePointerLockedElement(aElement, aDocument, nullptr);
nsContentUtils::DispatchEventOnlyToChrome(
aDocument, aElement, u"MozDOMPointerLock:Entered"_ns, CanBubble::eYes,
Cancelable::eNo, /* DefaultAction */ nullptr);
return true;
}
/* static */
bool PointerLockManager::SetPointerLock(Element* aElement, Document* aDocument,
StyleCursorKind aCursorStyle,
bool aUnadjustedMovement) {
MOZ_ASSERT(!aElement || aElement->OwnerDoc() == aDocument,
"We should be either unlocking pointer (aElement is nullptr), "
"or locking pointer to an element in this document");
#ifdef DEBUG
if (!aElement) {
nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
MOZ_ASSERT(pointerLockedDoc == aDocument);
}
#endif
PresShell* presShell = aDocument->GetPresShell();
if (!presShell) {
NS_WARNING("SetPointerLock(): No PresShell");
if (!aElement) {
sIsLocked = false;
sIsLockUnadjustedMovement = false;
// If we are unlocking pointer lock, but for some reason the doc
// has already detached from the presshell, just ask the event
// state manager to release the pointer.
EventStateManager::SetPointerLock(nullptr, nullptr,
/* aUnadjustedMovement */ false);
return true;
}
return false;
}
RefPtr<nsPresContext> presContext = presShell->GetPresContext();
if (!presContext) {
NS_WARNING("SetPointerLock(): Unable to get PresContext");
return false;
}
nsCOMPtr<nsIWidget> widget;
nsIFrame* rootFrame = presShell->GetRootFrame();
if (!NS_WARN_IF(!rootFrame)) {
widget = rootFrame->GetNearestWidget();
NS_WARNING_ASSERTION(widget,
"SetPointerLock(): Unable to find widget in "
"presShell->GetRootFrame()->GetNearestWidget();");
}
if (aElement && !widget) {
NS_WARNING("SetPointerLock(): No Widget while requesting pointer lock");
return false;
}
sIsLocked = !!aElement;
sIsLockUnadjustedMovement = !!aElement && aUnadjustedMovement;
// Hide the cursor and set pointer lock for future mouse events
RefPtr<EventStateManager> esm = presContext->EventStateManager();
esm->SetCursor(aCursorStyle, nullptr, {}, Nothing(), widget, true);
EventStateManager::SetPointerLock(widget, presContext,
sIsLockUnadjustedMovement);
return true;
}
/* static */
bool PointerLockManager::IsInLockContext(BrowsingContext* aContext) {
if (!aContext) {
return false;
}
nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
if (!pointerLockedDoc || !pointerLockedDoc->GetBrowsingContext()) {
return false;
}
BrowsingContext* lockTop = pointerLockedDoc->GetBrowsingContext()->Top();
BrowsingContext* top = aContext->Top();
return top == lockTop;
}
/* static */
void PointerLockManager::SetLockedRemoteTarget(BrowserParent* aBrowserParent,
const bool& aUnadjustedMovement,
nsACString& aError) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(aBrowserParent);
if (sLockedRemoteTarget) {
if (sLockedRemoteTarget != aBrowserParent) {
aError = "PointerLockDeniedInUse"_ns;
}
return;
}
// Check if any popup is open.
if (IsPopupOpened()) {
aError = "PointerLockDeniedFailedToLock"_ns;
return;
}
RefPtr<Element> element =
aBrowserParent->TopLevelBrowserParent()->GetOwnerElement();
if (NS_WARN_IF(!element)) {
aError = "PointerLockDeniedFailedToLock"_ns;
return;
}
nsPresContext* presContext = element->OwnerDoc()->GetPresContext();
if (NS_WARN_IF(!presContext)) {
aError = "PointerLockDeniedFailedToLock"_ns;
return;
}
nsIWidget* widget = nsContentUtils::WidgetForContent(element);
if (NS_WARN_IF(!widget)) {
aError = "PointerLockDeniedFailedToLock"_ns;
return;
}
MOZ_POINTERLOCK_LOG("Set locked remote target to 0x%p", aBrowserParent);
sLockedRemoteTarget = aBrowserParent;
PointerEventHandler::ReleaseAllPointerCaptureRemoteTarget();
if (StaticPrefs::dom_pointer_lock_reset_to_center_from_parent_enabled()) {
// Capture pointer to the top level browser element, so that mouse event can
// be always retargeted to the content when pointer lock is active, even if
// the pointer is out of the content area.
// XXX Now that we also capture the pointer in the parent process for
// pointer lock, we could potentially rely on the captured content for
// cross-process event dispatching instead of tracking the locked remote
// target separately.
PresShell::SetCapturingContent(element, CaptureFlags::PointerLock);
EventStateManager::RequestLockPointer(widget, presContext,
aUnadjustedMovement);
}
}
/* static */
void PointerLockManager::ReleaseLockedRemoteTarget(
BrowserParent* aBrowserParent) {
MOZ_ASSERT(XRE_IsParentProcess());
if (sLockedRemoteTarget == aBrowserParent) {
MOZ_POINTERLOCK_LOG("Release locked remote target 0x%p",
sLockedRemoteTarget);
sLockedRemoteTarget = nullptr;
PresShell::SetCapturingContent(nullptr, CaptureFlags::PointerLock);
nsCOMPtr<nsIWidget> widget = aBrowserParent->GetTopLevelWidget();
EventStateManager::ReleaseLockedPointer(widget);
}
}
/* static */
bool PointerLockManager::ShouldResetPointer() {
if (!StaticPrefs::dom_pointer_lock_reset_to_center_from_parent_enabled()) {
return IsLocked();
}
// If the pref is enabled, we should reset pointer position to center of the
// widget in parent process.
return XRE_IsParentProcess() && (GetLockedRemoteTarget() || IsLocked());
}
static nsIWidget* GetWidgetForDocument(Document* aDocument) {
if (!aDocument) {
return nullptr;
}
PresShell* presShell = aDocument->GetPresShell();
if (!presShell) {
return nullptr;
}
return presShell->GetRootWidget();
}
PointerLockManager::PointerLockRequest::PointerLockRequest(
Element* aElement, bool aUserInputOrChromeCaller, bool aUnadjustedMovement,
Promise* aPromise)
: mozilla::Runnable("PointerLockRequest"),
mElement(do_GetWeakReference(aElement)),
mDocument(do_GetWeakReference(aElement->OwnerDoc())),
mUserInputOrChromeCaller(aUserInputOrChromeCaller),
mUnadjustedMovement(aUnadjustedMovement),
mPromise(aPromise) {}
NS_IMETHODIMP
PointerLockManager::PointerLockRequest::Run() {
nsCOMPtr<Element> element = do_QueryReferent(mElement);
nsCOMPtr<Document> document = do_QueryReferent(mDocument);
RefPtr<Promise> promise = std::move(mPromise);
const char* error = nullptr;
if (!element || !document || !element->GetComposedDoc()) {
error = "PointerLockDeniedNotInDocument";
} else if (element->GetComposedDoc() != document) {
error = "PointerLockDeniedMovedDocument";
} else if (mUnadjustedMovement) {
nsCOMPtr<nsIWidget> widget = GetWidgetForDocument(document);
if (!widget || !widget->SupportsUnadjustedMovement()) {
// XXX Reuse the existing error code for now, we should have a more
// specific error code for this case.
error = "PointerLockDeniedFailedToLock";
}
}
if (!error) {
nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(sLockedElement);
// XXX The steps are not exactly the same as in the spec, but they should
// result in the same behavior.
if (element == pointerLockedElement &&
sIsLockUnadjustedMovement == mUnadjustedMovement) {
DispatchPointerLockChange(document);
promise->MaybeResolveWithUndefined();
return NS_OK;
}
// Note, we must bypass focus change, so pass true as the last parameter!
error = GetPointerLockError(element, pointerLockedElement, true);
// Another element in the same document is requesting pointer lock,
// just grant it without user input check.
if (!error && pointerLockedElement) {
// Apply new options on the existing lock.
if (sIsLockUnadjustedMovement != mUnadjustedMovement) {
nsCOMPtr<nsIWidget> widget = GetWidgetForDocument(document);
if (NS_WARN_IF(!widget)) {
FailWith(document, promise, "PointerLockDeniedFailedToLock");
return NS_OK;
}
MOZ_ASSERT(widget->SupportsUnadjustedMovement());
widget->SetNativePointerLockMode(
mUnadjustedMovement ? nsIWidget::NativePointerLockMode::Unadjusted
: nsIWidget::NativePointerLockMode::Regular);
sIsLockUnadjustedMovement = mUnadjustedMovement;
}
if (element != pointerLockedElement) {
ChangePointerLockedElement(element, document, pointerLockedElement);
} else {
DispatchPointerLockChange(document);
}
promise->MaybeResolveWithUndefined();
return NS_OK;
}
}
// If it is neither user input initiated, nor requested in fullscreen,
// it should be rejected.
if (!error && !mUserInputOrChromeCaller && !document->Fullscreen()) {
error = "PointerLockDeniedNotInputDriven";
}
if (error) {
FailWith(document, promise, error);
return NS_OK;
}
if (BrowserChild* browserChild =
BrowserChild::GetFrom(document->GetDocShell())) {
nsWeakPtr e = do_GetWeakReference(element);
nsWeakPtr doc = do_GetWeakReference(element->OwnerDoc());
nsWeakPtr bc = do_GetWeakReference(browserChild);
browserChild->SendRequestPointerLock(
mUnadjustedMovement,
[e, doc, bc, promise,
unadjustedMovement = mUnadjustedMovement](const nsCString& aError) {
nsCOMPtr<Document> document = do_QueryReferent(doc);
if (!aError.IsEmpty()) {
FailWith(document, promise, aError.get());
return;
}
const char* error = nullptr;
auto autoCleanup = MakeScopeExit([&] {
if (error) {
FailWith(document, promise, error);
// If we are failed to set pointer lock, notify parent to stop
// redirect mouse event to this process.
if (nsCOMPtr<nsIBrowserChild> browserChild =
do_QueryReferent(bc)) {
static_cast<BrowserChild*>(browserChild.get())
->SendReleasePointerLock();
}
}
});
nsCOMPtr<Element> element = do_QueryReferent(e);
if (!element || !document || !element->GetComposedDoc()) {
error = "PointerLockDeniedNotInDocument";
return;
}
if (element->GetComposedDoc() != document) {
error = "PointerLockDeniedMovedDocument";
return;
}
nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
error = GetPointerLockError(element, pointerLockedElement, true);
if (error) {
return;
}
if (!StartSetPointerLock(element, document, unadjustedMovement)) {
error = "PointerLockDeniedFailedToLock";
return;
}
promise->MaybeResolveWithUndefined();
},
[doc, promise](mozilla::ipc::ResponseRejectReason) {
// IPC layer error
nsCOMPtr<Document> document = do_QueryReferent(doc);
FailWith(document, promise, "PointerLockDeniedFailedToLock");
});
} else {
if (StartSetPointerLock(element, document, mUnadjustedMovement)) {
promise->MaybeResolveWithUndefined();
} else {
FailWith(document, promise, "PointerLockDeniedFailedToLock");
}
}
return NS_OK;
}
} // namespace mozilla