Bug 1835710 - Cancel off-thread JIT compilation before changing nursery allocation...
[gecko.git] / dom / base / PointerLockManager.cpp
blob78f1d600afe0b80a41ad1aaa3a29d4c03d4b1d66
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PointerLockManager.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/EventStateManager.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/StaticPrefs_full_screen_api.h"
14 #include "mozilla/dom/BindingDeclarations.h"
15 #include "mozilla/dom/BrowserChild.h"
16 #include "mozilla/dom/BrowserParent.h"
17 #include "mozilla/dom/BrowsingContext.h"
18 #include "mozilla/dom/Document.h"
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/WindowContext.h"
21 #include "nsCOMPtr.h"
22 #include "nsSandboxFlags.h"
24 namespace mozilla {
26 using mozilla::dom::BrowserChild;
27 using mozilla::dom::BrowserParent;
28 using mozilla::dom::BrowsingContext;
29 using mozilla::dom::CallerType;
30 using mozilla::dom::Document;
31 using mozilla::dom::Element;
32 using mozilla::dom::WindowContext;
34 // Reference to the pointer locked element.
35 static nsWeakPtr sLockedElement;
37 // Reference to the document which requested pointer lock.
38 static nsWeakPtr sLockedDoc;
40 // Reference to the BrowserParent requested pointer lock.
41 static BrowserParent* sLockedRemoteTarget = nullptr;
43 /* static */
44 bool PointerLockManager::sIsLocked = false;
46 /* static */
47 already_AddRefed<dom::Element> PointerLockManager::GetLockedElement() {
48 nsCOMPtr<Element> element = do_QueryReferent(sLockedElement);
49 return element.forget();
52 /* static */
53 already_AddRefed<dom::Document> PointerLockManager::GetLockedDocument() {
54 nsCOMPtr<Document> document = do_QueryReferent(sLockedDoc);
55 return document.forget();
58 /* static */
59 BrowserParent* PointerLockManager::GetLockedRemoteTarget() {
60 MOZ_ASSERT(XRE_IsParentProcess());
61 return sLockedRemoteTarget;
64 static void DispatchPointerLockChange(Document* aTarget) {
65 if (!aTarget) {
66 return;
69 RefPtr<AsyncEventDispatcher> asyncDispatcher =
70 new AsyncEventDispatcher(aTarget, u"pointerlockchange"_ns,
71 CanBubble::eYes, ChromeOnlyDispatch::eNo);
72 asyncDispatcher->PostDOMEvent();
75 static void DispatchPointerLockError(Document* aTarget, const char* aMessage) {
76 if (!aTarget) {
77 return;
80 RefPtr<AsyncEventDispatcher> asyncDispatcher =
81 new AsyncEventDispatcher(aTarget, u"pointerlockerror"_ns, CanBubble::eYes,
82 ChromeOnlyDispatch::eNo);
83 asyncDispatcher->PostDOMEvent();
84 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
85 aTarget, nsContentUtils::eDOM_PROPERTIES,
86 aMessage);
89 static const char* GetPointerLockError(Element* aElement, Element* aCurrentLock,
90 bool aNoFocusCheck = false) {
91 // Check if pointer lock pref is enabled
92 if (!StaticPrefs::full_screen_api_pointer_lock_enabled()) {
93 return "PointerLockDeniedDisabled";
96 nsCOMPtr<Document> ownerDoc = aElement->OwnerDoc();
97 if (aCurrentLock && aCurrentLock->OwnerDoc() != ownerDoc) {
98 return "PointerLockDeniedInUse";
101 if (!aElement->IsInComposedDoc()) {
102 return "PointerLockDeniedNotInDocument";
105 if (ownerDoc->GetSandboxFlags() & SANDBOXED_POINTER_LOCK) {
106 return "PointerLockDeniedSandboxed";
109 // Check if the element is in a document with a docshell.
110 if (!ownerDoc->GetContainer()) {
111 return "PointerLockDeniedHidden";
113 nsCOMPtr<nsPIDOMWindowOuter> ownerWindow = ownerDoc->GetWindow();
114 if (!ownerWindow) {
115 return "PointerLockDeniedHidden";
117 nsCOMPtr<nsPIDOMWindowInner> ownerInnerWindow = ownerDoc->GetInnerWindow();
118 if (!ownerInnerWindow) {
119 return "PointerLockDeniedHidden";
121 if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
122 return "PointerLockDeniedHidden";
125 BrowsingContext* bc = ownerDoc->GetBrowsingContext();
126 BrowsingContext* topBC = bc ? bc->Top() : nullptr;
127 WindowContext* topWC = ownerDoc->GetTopLevelWindowContext();
128 if (!topBC || !topBC->IsActive() || !topWC ||
129 topWC != topBC->GetCurrentWindowContext()) {
130 return "PointerLockDeniedHidden";
133 if (!aNoFocusCheck) {
134 if (!IsInActiveTab(ownerDoc)) {
135 return "PointerLockDeniedNotFocused";
139 return nullptr;
142 /* static */
143 void PointerLockManager::RequestLock(Element* aElement,
144 CallerType aCallerType) {
145 NS_ASSERTION(aElement,
146 "Must pass non-null element to PointerLockManager::RequestLock");
148 RefPtr<Document> doc = aElement->OwnerDoc();
149 nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
150 if (aElement == pointerLockedElement) {
151 DispatchPointerLockChange(doc);
152 return;
155 if (const char* msg = GetPointerLockError(aElement, pointerLockedElement)) {
156 DispatchPointerLockError(doc, msg);
157 return;
160 bool userInputOrSystemCaller =
161 doc->HasValidTransientUserGestureActivation() ||
162 aCallerType == CallerType::System;
163 nsCOMPtr<nsIRunnable> request =
164 new PointerLockRequest(aElement, userInputOrSystemCaller);
165 doc->Dispatch(TaskCategory::Other, request.forget());
168 /* static */
169 void PointerLockManager::Unlock(Document* aDoc) {
170 if (!sIsLocked) {
171 return;
174 nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
175 if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
176 return;
178 if (!SetPointerLock(nullptr, pointerLockedDoc, StyleCursorKind::Auto)) {
179 return;
182 nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
183 ChangePointerLockedElement(nullptr, pointerLockedDoc, pointerLockedElement);
185 if (BrowserChild* browserChild =
186 BrowserChild::GetFrom(pointerLockedDoc->GetDocShell())) {
187 browserChild->SendReleasePointerLock();
190 AsyncEventDispatcher::RunDOMEventWhenSafe(
191 *pointerLockedElement, u"MozDOMPointerLock:Exited"_ns, CanBubble::eYes,
192 ChromeOnlyDispatch::eYes);
195 /* static */
196 void PointerLockManager::ChangePointerLockedElement(
197 Element* aElement, Document* aDocument, Element* aPointerLockedElement) {
198 // aDocument here is not really necessary, as it is the uncomposed
199 // document of both aElement and aPointerLockedElement as far as one
200 // is not nullptr, and they wouldn't both be nullptr in any case.
201 // But since the caller of this function should have known what the
202 // document is, we just don't try to figure out what it should be.
203 MOZ_ASSERT(aDocument);
204 MOZ_ASSERT(aElement != aPointerLockedElement);
205 if (aPointerLockedElement) {
206 MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument);
207 aPointerLockedElement->ClearPointerLock();
209 if (aElement) {
210 MOZ_ASSERT(aElement->GetComposedDoc() == aDocument);
211 aElement->SetPointerLock();
212 sLockedElement = do_GetWeakReference(aElement);
213 sLockedDoc = do_GetWeakReference(aDocument);
214 NS_ASSERTION(sLockedElement && sLockedDoc,
215 "aElement and this should support weak references!");
216 } else {
217 sLockedElement = nullptr;
218 sLockedDoc = nullptr;
220 // Retarget all events to aElement via capture or
221 // stop retargeting if aElement is nullptr.
222 PresShell::SetCapturingContent(aElement, CaptureFlags::PointerLock);
223 DispatchPointerLockChange(aDocument);
226 /* static */
227 bool PointerLockManager::StartSetPointerLock(Element* aElement,
228 Document* aDocument) {
229 if (!SetPointerLock(aElement, aDocument, StyleCursorKind::None)) {
230 DispatchPointerLockError(aDocument, "PointerLockDeniedFailedToLock");
231 return false;
234 ChangePointerLockedElement(aElement, aDocument, nullptr);
235 nsContentUtils::DispatchEventOnlyToChrome(
236 aDocument, ToSupports(aElement), u"MozDOMPointerLock:Entered"_ns,
237 CanBubble::eYes, Cancelable::eNo, /* DefaultAction */ nullptr);
239 return true;
242 /* static */
243 bool PointerLockManager::SetPointerLock(Element* aElement, Document* aDocument,
244 StyleCursorKind aCursorStyle) {
245 MOZ_ASSERT(!aElement || aElement->OwnerDoc() == aDocument,
246 "We should be either unlocking pointer (aElement is nullptr), "
247 "or locking pointer to an element in this document");
248 #ifdef DEBUG
249 if (!aElement) {
250 nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
251 MOZ_ASSERT(pointerLockedDoc == aDocument);
253 #endif
255 PresShell* presShell = aDocument->GetPresShell();
256 if (!presShell) {
257 NS_WARNING("SetPointerLock(): No PresShell");
258 if (!aElement) {
259 sIsLocked = false;
260 // If we are unlocking pointer lock, but for some reason the doc
261 // has already detached from the presshell, just ask the event
262 // state manager to release the pointer.
263 EventStateManager::SetPointerLock(nullptr, nullptr);
264 return true;
266 return false;
268 nsPresContext* presContext = presShell->GetPresContext();
269 if (!presContext) {
270 NS_WARNING("SetPointerLock(): Unable to get PresContext");
271 return false;
274 nsCOMPtr<nsIWidget> widget;
275 nsIFrame* rootFrame = presShell->GetRootFrame();
276 if (!NS_WARN_IF(!rootFrame)) {
277 widget = rootFrame->GetNearestWidget();
278 NS_WARNING_ASSERTION(widget,
279 "SetPointerLock(): Unable to find widget in "
280 "presShell->GetRootFrame()->GetNearestWidget();");
281 if (aElement && !widget) {
282 return false;
286 sIsLocked = !!aElement;
288 // Hide the cursor and set pointer lock for future mouse events
289 RefPtr<EventStateManager> esm = presContext->EventStateManager();
290 esm->SetCursor(aCursorStyle, nullptr, {}, Nothing(), widget, true);
291 EventStateManager::SetPointerLock(widget, aElement);
293 return true;
296 /* static */
297 bool PointerLockManager::IsInLockContext(BrowsingContext* aContext) {
298 if (!aContext) {
299 return false;
302 nsCOMPtr<Document> pointerLockedDoc = GetLockedDocument();
303 if (!pointerLockedDoc || !pointerLockedDoc->GetBrowsingContext()) {
304 return false;
307 BrowsingContext* lockTop = pointerLockedDoc->GetBrowsingContext()->Top();
308 BrowsingContext* top = aContext->Top();
310 return top == lockTop;
313 /* static */
314 bool PointerLockManager::SetLockedRemoteTarget(BrowserParent* aBrowserParent) {
315 MOZ_ASSERT(XRE_IsParentProcess());
316 if (sLockedRemoteTarget) {
317 return sLockedRemoteTarget == aBrowserParent;
320 sLockedRemoteTarget = aBrowserParent;
321 return true;
324 /* static */
325 void PointerLockManager::ReleaseLockedRemoteTarget(
326 BrowserParent* aBrowserParent) {
327 MOZ_ASSERT(XRE_IsParentProcess());
328 if (sLockedRemoteTarget == aBrowserParent) {
329 sLockedRemoteTarget = nullptr;
333 PointerLockManager::PointerLockRequest::PointerLockRequest(
334 Element* aElement, bool aUserInputOrChromeCaller)
335 : mozilla::Runnable("PointerLockRequest"),
336 mElement(do_GetWeakReference(aElement)),
337 mDocument(do_GetWeakReference(aElement->OwnerDoc())),
338 mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
340 NS_IMETHODIMP
341 PointerLockManager::PointerLockRequest::Run() {
342 nsCOMPtr<Element> element = do_QueryReferent(mElement);
343 nsCOMPtr<Document> document = do_QueryReferent(mDocument);
345 const char* error = nullptr;
346 if (!element || !document || !element->GetComposedDoc()) {
347 error = "PointerLockDeniedNotInDocument";
348 } else if (element->GetComposedDoc() != document) {
349 error = "PointerLockDeniedMovedDocument";
351 if (!error) {
352 nsCOMPtr<Element> pointerLockedElement = do_QueryReferent(sLockedElement);
353 if (element == pointerLockedElement) {
354 DispatchPointerLockChange(document);
355 return NS_OK;
357 // Note, we must bypass focus change, so pass true as the last parameter!
358 error = GetPointerLockError(element, pointerLockedElement, true);
359 // Another element in the same document is requesting pointer lock,
360 // just grant it without user input check.
361 if (!error && pointerLockedElement) {
362 ChangePointerLockedElement(element, document, pointerLockedElement);
363 return NS_OK;
366 // If it is neither user input initiated, nor requested in fullscreen,
367 // it should be rejected.
368 if (!error && !mUserInputOrChromeCaller && !document->Fullscreen()) {
369 error = "PointerLockDeniedNotInputDriven";
372 if (error) {
373 DispatchPointerLockError(document, error);
374 return NS_OK;
377 if (BrowserChild* browserChild =
378 BrowserChild::GetFrom(document->GetDocShell())) {
379 nsWeakPtr e = do_GetWeakReference(element);
380 nsWeakPtr doc = do_GetWeakReference(element->OwnerDoc());
381 nsWeakPtr bc = do_GetWeakReference(browserChild);
382 browserChild->SendRequestPointerLock(
383 [e, doc, bc](const nsCString& aError) {
384 nsCOMPtr<Document> document = do_QueryReferent(doc);
385 if (!aError.IsEmpty()) {
386 DispatchPointerLockError(document, aError.get());
387 return;
390 const char* error = nullptr;
391 auto autoCleanup = MakeScopeExit([&] {
392 if (error) {
393 DispatchPointerLockError(document, error);
394 // If we are failed to set pointer lock, notify parent to stop
395 // redirect mouse event to this process.
396 if (nsCOMPtr<nsIBrowserChild> browserChild =
397 do_QueryReferent(bc)) {
398 static_cast<BrowserChild*>(browserChild.get())
399 ->SendReleasePointerLock();
404 nsCOMPtr<Element> element = do_QueryReferent(e);
405 if (!element || !document || !element->GetComposedDoc()) {
406 error = "PointerLockDeniedNotInDocument";
407 return;
410 if (element->GetComposedDoc() != document) {
411 error = "PointerLockDeniedMovedDocument";
412 return;
415 nsCOMPtr<Element> pointerLockedElement = GetLockedElement();
416 error = GetPointerLockError(element, pointerLockedElement, true);
417 if (error) {
418 return;
421 if (!StartSetPointerLock(element, document)) {
422 error = "PointerLockDeniedFailedToLock";
423 return;
426 [doc](mozilla::ipc::ResponseRejectReason) {
427 // IPC layer error
428 nsCOMPtr<Document> document = do_QueryReferent(doc);
429 if (!document) {
430 return;
433 DispatchPointerLockError(document, "PointerLockDeniedFailedToLock");
435 } else {
436 StartSetPointerLock(element, document);
439 return NS_OK;
442 } // namespace mozilla