- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 86 %
- : 85 %
- : 86 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 86 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 86 %
- : 85 %
- : 85 %
- : 86 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 82 %
- : 85 %
- : 85 %
- : 85 %
- : 85 %
- : 86 %
- : 86 %
- : 85 %
- : 85 %
- : 85 %
- : 86 %
- : 86 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 30 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 90 %
- : 95 %
- : 90 %
- : 90 %
- : 90 %
- : 95 %
- : 95 %
- : 90 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 90 %
- : 95 %
- : 95 %
- : 90 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 90 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 95 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 85 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 86 %
- : 86 %
- : 87 %
- : 87 %
- : 87 %
- : 86 %
- : 87 %
- : 86 %
- : 86 %
- : 87 %
- : 86 %
- : 87 %
- : 87 %
- : 82 %
- : 82 %
- : 82 %
- : 79 %
- : 80 %
- : 79 %
- : 80 %
- : 82 %
- : 82 %
- : 82 %
- : 80 %
- : 80 %
- : 79 %
- : 80 %
- : 82 %
- : 79 %
- : 80 %
- : 82 %
- : 80 %
- : 82 %
- : 80 %
- : 79 %
- : 82 %
- : 79 %
- : 80 %
- : 79 %
- : 79 %
Source code
Revision control
Copy as Markdown
Other Tools
/* 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
#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
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
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");
// Spec does this check in queued task instead, see step 6-2 of
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