Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / accessible / base / FocusManager.cpp
blob57cfd7034e1eb921cac9272aab030aeeed2c3b9f
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "FocusManager.h"
7 #include "LocalAccessible-inl.h"
8 #include "DocAccessible-inl.h"
9 #include "nsAccessibilityService.h"
10 #include "nsEventShell.h"
12 #include "nsFocusManager.h"
13 #include "mozilla/a11y/DocAccessibleParent.h"
14 #include "mozilla/EventStateManager.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/BrowsingContext.h"
17 #include "mozilla/dom/BrowserParent.h"
19 namespace mozilla {
20 namespace a11y {
22 FocusManager::FocusManager() {}
24 FocusManager::~FocusManager() {}
26 LocalAccessible* FocusManager::FocusedLocalAccessible() const {
27 MOZ_ASSERT(NS_IsMainThread());
28 if (mActiveItem) {
29 if (mActiveItem->IsDefunct()) {
30 MOZ_ASSERT_UNREACHABLE("Stored active item is unbound from document");
31 return nullptr;
34 return mActiveItem;
37 if (nsAccessibilityService::IsShutdown()) {
38 // We might try to get or create a DocAccessible below, which isn't safe (or
39 // useful) if the accessibility service is shutting down.
40 return nullptr;
43 nsINode* focusedNode = FocusedDOMNode();
44 if (focusedNode) {
45 DocAccessible* doc =
46 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
47 return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode)
48 : nullptr;
51 return nullptr;
54 Accessible* FocusManager::FocusedAccessible() const {
55 #if defined(ANDROID)
56 // It's not safe to call FocusedLocalAccessible() except on the main thread.
57 // Android might query RemoteAccessibles on the UI thread, which might call
58 // FocusedAccessible(). Never try to get the focused LocalAccessible in this
59 // case.
60 if (NS_IsMainThread()) {
61 if (Accessible* focusedAcc = FocusedLocalAccessible()) {
62 return focusedAcc;
64 } else {
65 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
67 return mFocusedRemoteDoc ? mFocusedRemoteDoc->GetFocusedAcc() : nullptr;
68 #else
69 if (Accessible* focusedAcc = FocusedLocalAccessible()) {
70 return focusedAcc;
73 if (!XRE_IsParentProcess()) {
74 // DocAccessibleParent's don't exist in the content
75 // process, so we can't return anything useful if this
76 // is the case.
77 return nullptr;
80 nsFocusManager* focusManagerDOM = nsFocusManager::GetFocusManager();
81 if (!focusManagerDOM) {
82 return nullptr;
85 // If we call GetFocusedBrowsingContext from the chrome process
86 // it returns the BrowsingContext for the focused _window_, which
87 // is not helpful here. Instead use GetFocusedBrowsingContextInChrome
88 // which returns the content BrowsingContext that has focus.
89 dom::BrowsingContext* focusedContext =
90 focusManagerDOM->GetFocusedBrowsingContextInChrome();
92 DocAccessibleParent* focusedDoc =
93 DocAccessibleParent::GetFrom(focusedContext);
94 return focusedDoc ? focusedDoc->GetFocusedAcc() : nullptr;
95 #endif // defined(ANDROID)
98 bool FocusManager::IsFocusWithin(const Accessible* aContainer) const {
99 Accessible* child = FocusedAccessible();
100 while (child) {
101 if (child == aContainer) return true;
103 child = child->Parent();
105 return false;
108 FocusManager::FocusDisposition FocusManager::IsInOrContainsFocus(
109 const LocalAccessible* aAccessible) const {
110 LocalAccessible* focus = FocusedLocalAccessible();
111 if (!focus) return eNone;
113 // If focused.
114 if (focus == aAccessible) return eFocused;
116 // If contains the focus.
117 LocalAccessible* child = focus->LocalParent();
118 while (child) {
119 if (child == aAccessible) return eContainsFocus;
121 child = child->LocalParent();
124 // If contained by focus.
125 child = aAccessible->LocalParent();
126 while (child) {
127 if (child == focus) return eContainedByFocus;
129 child = child->LocalParent();
132 return eNone;
135 bool FocusManager::WasLastFocused(const LocalAccessible* aAccessible) const {
136 return mLastFocus == aAccessible;
139 void FocusManager::NotifyOfDOMFocus(nsISupports* aTarget) {
140 #ifdef A11Y_LOG
141 if (logging::IsEnabled(logging::eFocus)) {
142 logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
144 #endif
146 mActiveItem = nullptr;
148 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
149 if (targetNode) {
150 DocAccessible* document =
151 GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
152 if (document) {
153 // Set selection listener for focused element.
154 if (targetNode->IsElement()) {
155 SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
158 document->HandleNotification<FocusManager, nsINode>(
159 this, &FocusManager::ProcessDOMFocus, targetNode);
164 void FocusManager::NotifyOfDOMBlur(nsISupports* aTarget) {
165 #ifdef A11Y_LOG
166 if (logging::IsEnabled(logging::eFocus)) {
167 logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
169 #endif
171 mActiveItem = nullptr;
173 // If DOM document stays focused then fire accessible focus event to process
174 // the case when no element within this DOM document will be focused.
175 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
176 if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
177 dom::Document* DOMDoc = targetNode->OwnerDoc();
178 DocAccessible* document = GetAccService()->GetDocAccessible(DOMDoc);
179 if (document) {
180 // Clear selection listener for previously focused element.
181 if (targetNode->IsElement()) {
182 SelectionMgr()->ClearControlSelectionListener();
185 document->HandleNotification<FocusManager, nsINode>(
186 this, &FocusManager::ProcessDOMFocus, DOMDoc);
191 void FocusManager::ActiveItemChanged(LocalAccessible* aItem,
192 bool aCheckIfActive) {
193 #ifdef A11Y_LOG
194 if (logging::IsEnabled(logging::eFocus)) {
195 logging::FocusNotificationTarget("active item changed", "Item", aItem);
197 #endif
199 // Nothing changed, happens for XUL trees and HTML selects.
200 if (aItem && aItem == mActiveItem) {
201 return;
204 mActiveItem = nullptr;
206 if (aItem && aCheckIfActive) {
207 LocalAccessible* widget = aItem->ContainerWidget();
208 #ifdef A11Y_LOG
209 if (logging::IsEnabled(logging::eFocus)) logging::ActiveWidget(widget);
210 #endif
211 if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable()) {
212 return;
215 mActiveItem = aItem;
217 // If mActiveItem is null we may need to shift a11y focus back to a remote
218 // document. For example, when combobox popup is closed, then
219 // the focus should be moved back to the combobox.
220 if (!mActiveItem && XRE_IsParentProcess()) {
221 dom::BrowserParent* browser = dom::BrowserParent::GetFocused();
222 if (browser) {
223 a11y::DocAccessibleParent* dap = browser->GetTopLevelDocAccessible();
224 if (dap) {
225 Unused << dap->SendRestoreFocus();
230 // If active item is changed then fire accessible focus event on it, otherwise
231 // if there's no an active item then fire focus event to accessible having
232 // DOM focus.
233 LocalAccessible* target = FocusedLocalAccessible();
234 if (target) {
235 DispatchFocusEvent(target->Document(), target);
239 void FocusManager::ForceFocusEvent() {
240 nsINode* focusedNode = FocusedDOMNode();
241 if (focusedNode) {
242 DocAccessible* document =
243 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
244 if (document) {
245 document->HandleNotification<FocusManager, nsINode>(
246 this, &FocusManager::ProcessDOMFocus, focusedNode);
251 void FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
252 LocalAccessible* aTarget) {
253 MOZ_ASSERT(aDocument, "No document for focused accessible!");
254 if (aDocument) {
255 RefPtr<AccEvent> event =
256 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget, eAutoDetect,
257 AccEvent::eCoalesceOfSameType);
258 aDocument->FireDelayedEvent(event);
259 mLastFocus = aTarget;
260 if (mActiveItem != aTarget) {
261 // This new focus overrides the stored active item, so clear the active
262 // item. Among other things, the old active item might die.
263 mActiveItem = nullptr;
266 #ifdef A11Y_LOG
267 if (logging::IsEnabled(logging::eFocus)) logging::FocusDispatched(aTarget);
268 #endif
272 void FocusManager::ProcessDOMFocus(nsINode* aTarget) {
273 #ifdef A11Y_LOG
274 if (logging::IsEnabled(logging::eFocus)) {
275 logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
277 #endif
279 DocAccessible* document =
280 GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
281 if (!document) return;
283 LocalAccessible* target =
284 document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
285 if (target) {
286 // Check if still focused. Otherwise we can end up with storing the active
287 // item for control that isn't focused anymore.
288 nsINode* focusedNode = FocusedDOMNode();
289 if (!focusedNode) return;
291 LocalAccessible* DOMFocus =
292 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
293 if (target != DOMFocus) return;
295 LocalAccessible* activeItem = target->CurrentItem();
296 if (activeItem) {
297 mActiveItem = activeItem;
298 target = activeItem;
301 DispatchFocusEvent(document, target);
305 void FocusManager::ProcessFocusEvent(AccEvent* aEvent) {
306 MOZ_ASSERT(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
307 "Focus event is expected!");
309 // Emit focus event if event target is the active item. Otherwise then check
310 // if it's still focused and then update active item and emit focus event.
311 LocalAccessible* target = aEvent->GetAccessible();
312 MOZ_ASSERT(!target->IsDefunct());
313 if (target != mActiveItem) {
314 // Check if still focused. Otherwise we can end up with storing the active
315 // item for control that isn't focused anymore.
316 DocAccessible* document = aEvent->Document();
317 nsINode* focusedNode = FocusedDOMNode();
318 if (!focusedNode) return;
320 LocalAccessible* DOMFocus =
321 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
322 if (target != DOMFocus) return;
324 LocalAccessible* activeItem = target->CurrentItem();
325 if (activeItem) {
326 mActiveItem = activeItem;
327 target = activeItem;
328 MOZ_ASSERT(!target->IsDefunct());
332 // Fire menu start/end events for ARIA menus.
333 if (target->IsARIARole(nsGkAtoms::menuitem)) {
334 // The focus was moved into menu.
335 LocalAccessible* ARIAMenubar = nullptr;
336 for (LocalAccessible* parent = target->LocalParent(); parent;
337 parent = parent->LocalParent()) {
338 if (parent->IsARIARole(nsGkAtoms::menubar)) {
339 ARIAMenubar = parent;
340 break;
343 // Go up in the parent chain of the menu hierarchy.
344 if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
345 !parent->IsARIARole(nsGkAtoms::menu)) {
346 break;
350 if (ARIAMenubar != mActiveARIAMenubar) {
351 // Leaving ARIA menu. Fire menu_end event on current menubar.
352 if (mActiveARIAMenubar) {
353 RefPtr<AccEvent> menuEndEvent =
354 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
355 aEvent->FromUserInput());
356 nsEventShell::FireEvent(menuEndEvent);
359 mActiveARIAMenubar = ARIAMenubar;
361 // Entering ARIA menu. Fire menu_start event.
362 if (mActiveARIAMenubar) {
363 RefPtr<AccEvent> menuStartEvent =
364 new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
365 mActiveARIAMenubar, aEvent->FromUserInput());
366 nsEventShell::FireEvent(menuStartEvent);
369 } else if (mActiveARIAMenubar) {
370 // Focus left a menu. Fire menu_end event.
371 RefPtr<AccEvent> menuEndEvent =
372 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
373 aEvent->FromUserInput());
374 nsEventShell::FireEvent(menuEndEvent);
376 mActiveARIAMenubar = nullptr;
379 #ifdef A11Y_LOG
380 if (logging::IsEnabled(logging::eFocus)) {
381 logging::FocusNotificationTarget("fire focus event", "Target", target);
383 #endif
385 // Reset cached caret value. The cache will be updated upon processing the
386 // next caret move event. This ensures that we will return the correct caret
387 // offset before the caret move event is handled.
388 SelectionMgr()->ResetCaretOffset();
390 RefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS,
391 target, aEvent->FromUserInput());
392 nsEventShell::FireEvent(focusEvent);
394 if (NS_WARN_IF(target->IsDefunct())) {
395 // target died during nsEventShell::FireEvent.
396 return;
399 // Fire scrolling_start event when the document receives the focus if it has
400 // an anchor jump. If an accessible within the document receive the focus
401 // then null out the anchor jump because it no longer applies.
402 DocAccessible* targetDocument = target->Document();
403 MOZ_ASSERT(targetDocument);
404 LocalAccessible* anchorJump = targetDocument->AnchorJump();
405 if (anchorJump) {
406 if (target == targetDocument || target->IsNonInteractive()) {
407 // XXX: bug 625699, note in some cases the node could go away before we
408 // we receive focus event, for example if the node is removed from DOM.
409 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
410 anchorJump, aEvent->FromUserInput());
412 targetDocument->SetAnchorJump(nullptr);
416 nsINode* FocusManager::FocusedDOMNode() const {
417 nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
418 nsIContent* focusedElm = DOMFocusManager->GetFocusedElement();
419 nsIFrame* focusedFrame = focusedElm ? focusedElm->GetPrimaryFrame() : nullptr;
420 // DOM elements retain their focused state when they get styled as display:
421 // none/content or visibility: hidden. We should treat those cases as if those
422 // elements were removed, and focus on doc.
423 if (focusedFrame && focusedFrame->StyleVisibility()->IsVisible()) {
424 // Print preview documents don't get DocAccessibles, but we still want a11y
425 // focus to go somewhere useful. Therefore, we allow a11y focus to land on
426 // the OuterDocAccessible in this case.
427 // Note that this code only handles remote print preview documents.
428 if (EventStateManager::IsTopLevelRemoteTarget(focusedElm) &&
429 focusedElm->AsElement()->HasAttribute(u"printpreview"_ns)) {
430 return focusedElm;
432 // No focus on remote target elements like xul:browser having DOM focus and
433 // residing in chrome process because it means an element in content process
434 // keeps the focus. Similarly, suppress focus on OOP iframes because an
435 // element in another content process should now have the focus.
436 if (EventStateManager::IsRemoteTarget(focusedElm)) {
437 return nullptr;
439 return focusedElm;
442 // Otherwise the focus can be on DOM document.
443 dom::BrowsingContext* context = DOMFocusManager->GetFocusedBrowsingContext();
444 if (context) {
445 // GetDocShell will return null if the document isn't in our process.
446 nsIDocShell* shell = context->GetDocShell();
447 if (shell) {
448 return shell->GetDocument();
452 // Focus isn't in this process.
453 return nullptr;
456 dom::Document* FocusManager::FocusedDOMDocument() const {
457 nsINode* focusedNode = FocusedDOMNode();
458 return focusedNode ? focusedNode->OwnerDoc() : nullptr;
461 } // namespace a11y
462 } // namespace mozilla