Bumping manifests a=b2g-bump
[gecko.git] / accessible / base / FocusManager.cpp
blob0fae8c5407607a5264120c5ba1353f4f47efc267
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 "Accessible-inl.h"
8 #include "AccIterator.h"
9 #include "DocAccessible-inl.h"
10 #include "nsAccessibilityService.h"
11 #include "nsAccUtils.h"
12 #include "nsEventShell.h"
13 #include "Role.h"
15 #include "nsFocusManager.h"
16 #include "mozilla/EventStateManager.h"
17 #include "mozilla/dom/Element.h"
19 namespace mozilla {
20 namespace a11y {
22 FocusManager::FocusManager()
26 FocusManager::~FocusManager()
30 Accessible*
31 FocusManager::FocusedAccessible() const
33 if (mActiveItem)
34 return mActiveItem;
36 nsINode* focusedNode = FocusedDOMNode();
37 if (focusedNode) {
38 DocAccessible* doc =
39 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
40 return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
43 return nullptr;
46 bool
47 FocusManager::IsFocused(const Accessible* aAccessible) const
49 if (mActiveItem)
50 return mActiveItem == aAccessible;
52 nsINode* focusedNode = FocusedDOMNode();
53 if (focusedNode) {
54 // XXX: Before getting an accessible for node having a DOM focus make sure
55 // they belong to the same document because it can trigger unwanted document
56 // accessible creation for temporary about:blank document. Without this
57 // peculiarity we would end up with plain implementation based on
58 // FocusedAccessible() method call. Make sure this issue is fixed in
59 // bug 638465.
60 if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
61 DocAccessible* doc =
62 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
63 return aAccessible ==
64 (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
67 return false;
70 bool
71 FocusManager::IsFocusWithin(const Accessible* aContainer) const
73 Accessible* child = FocusedAccessible();
74 while (child) {
75 if (child == aContainer)
76 return true;
78 child = child->Parent();
80 return false;
83 FocusManager::FocusDisposition
84 FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
86 Accessible* focus = FocusedAccessible();
87 if (!focus)
88 return eNone;
90 // If focused.
91 if (focus == aAccessible)
92 return eFocused;
94 // If contains the focus.
95 Accessible* child = focus->Parent();
96 while (child) {
97 if (child == aAccessible)
98 return eContainsFocus;
100 child = child->Parent();
103 // If contained by focus.
104 child = aAccessible->Parent();
105 while (child) {
106 if (child == focus)
107 return eContainedByFocus;
109 child = child->Parent();
112 return eNone;
115 void
116 FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
118 #ifdef A11Y_LOG
119 if (logging::IsEnabled(logging::eFocus))
120 logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
121 #endif
123 mActiveItem = nullptr;
125 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
126 if (targetNode) {
127 DocAccessible* document =
128 GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
129 if (document) {
130 // Set selection listener for focused element.
131 if (targetNode->IsElement())
132 SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
134 document->HandleNotification<FocusManager, nsINode>
135 (this, &FocusManager::ProcessDOMFocus, targetNode);
140 void
141 FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
143 #ifdef A11Y_LOG
144 if (logging::IsEnabled(logging::eFocus))
145 logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
146 #endif
148 mActiveItem = nullptr;
150 // If DOM document stays focused then fire accessible focus event to process
151 // the case when no element within this DOM document will be focused.
152 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
153 if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
154 nsIDocument* DOMDoc = targetNode->OwnerDoc();
155 DocAccessible* document =
156 GetAccService()->GetDocAccessible(DOMDoc);
157 if (document) {
158 // Clear selection listener for previously focused element.
159 if (targetNode->IsElement())
160 SelectionMgr()->ClearControlSelectionListener();
162 document->HandleNotification<FocusManager, nsINode>
163 (this, &FocusManager::ProcessDOMFocus, DOMDoc);
168 void
169 FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
171 #ifdef A11Y_LOG
172 if (logging::IsEnabled(logging::eFocus))
173 logging::FocusNotificationTarget("active item changed", "Item", aItem);
174 #endif
176 // Nothing changed, happens for XUL trees and HTML selects.
177 if (aItem && aItem == mActiveItem)
178 return;
180 mActiveItem = nullptr;
182 if (aItem && aCheckIfActive) {
183 Accessible* widget = aItem->ContainerWidget();
184 #ifdef A11Y_LOG
185 if (logging::IsEnabled(logging::eFocus))
186 logging::ActiveWidget(widget);
187 #endif
188 if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
189 return;
191 mActiveItem = aItem;
193 // If active item is changed then fire accessible focus event on it, otherwise
194 // if there's no an active item then fire focus event to accessible having
195 // DOM focus.
196 Accessible* target = FocusedAccessible();
197 if (target)
198 DispatchFocusEvent(target->Document(), target);
201 void
202 FocusManager::ForceFocusEvent()
204 nsINode* focusedNode = FocusedDOMNode();
205 if (focusedNode) {
206 DocAccessible* document =
207 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
208 if (document) {
209 document->HandleNotification<FocusManager, nsINode>
210 (this, &FocusManager::ProcessDOMFocus, focusedNode);
215 void
216 FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
217 Accessible* aTarget)
219 NS_PRECONDITION(aDocument, "No document for focused accessible!");
220 if (aDocument) {
221 nsRefPtr<AccEvent> event =
222 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
223 eAutoDetect, AccEvent::eCoalesceOfSameType);
224 aDocument->FireDelayedEvent(event);
226 #ifdef A11Y_LOG
227 if (logging::IsEnabled(logging::eFocus))
228 logging::FocusDispatched(aTarget);
229 #endif
233 void
234 FocusManager::ProcessDOMFocus(nsINode* aTarget)
236 #ifdef A11Y_LOG
237 if (logging::IsEnabled(logging::eFocus))
238 logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
239 #endif
241 DocAccessible* document =
242 GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
243 if (!document)
244 return;
246 Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
247 if (target) {
248 // Check if still focused. Otherwise we can end up with storing the active
249 // item for control that isn't focused anymore.
250 nsINode* focusedNode = FocusedDOMNode();
251 if (!focusedNode)
252 return;
254 Accessible* DOMFocus =
255 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
256 if (target != DOMFocus)
257 return;
259 Accessible* activeItem = target->CurrentItem();
260 if (activeItem) {
261 mActiveItem = activeItem;
262 target = activeItem;
265 DispatchFocusEvent(document, target);
269 void
270 FocusManager::ProcessFocusEvent(AccEvent* aEvent)
272 NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
273 "Focus event is expected!");
275 // Emit focus event if event target is the active item. Otherwise then check
276 // if it's still focused and then update active item and emit focus event.
277 Accessible* target = aEvent->GetAccessible();
278 if (target != mActiveItem) {
280 // Check if still focused. Otherwise we can end up with storing the active
281 // item for control that isn't focused anymore.
282 DocAccessible* document = aEvent->GetDocAccessible();
283 nsINode* focusedNode = FocusedDOMNode();
284 if (!focusedNode)
285 return;
287 Accessible* DOMFocus =
288 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
289 if (target != DOMFocus)
290 return;
292 Accessible* activeItem = target->CurrentItem();
293 if (activeItem) {
294 mActiveItem = activeItem;
295 target = activeItem;
299 // Fire menu start/end events for ARIA menus.
300 if (target->IsARIARole(nsGkAtoms::menuitem)) {
301 // The focus was moved into menu.
302 bool tryOwnsParent = true;
303 Accessible* ARIAMenubar = nullptr;
304 Accessible* child = target;
305 Accessible* parent = child->Parent();
306 while (parent) {
307 nsRoleMapEntry* roleMap = parent->ARIARoleMap();
308 if (roleMap) {
309 if (roleMap->Is(nsGkAtoms::menubar)) {
310 ARIAMenubar = parent;
311 break;
314 // Go up in the parent chain of the menu hierarchy.
315 if (roleMap->Is(nsGkAtoms::menuitem) || roleMap->Is(nsGkAtoms::menu)) {
316 child = parent;
317 parent = child->Parent();
318 tryOwnsParent = true;
319 continue;
323 // If no required context role then check aria-owns relation.
324 if (!tryOwnsParent)
325 break;
327 RelatedAccIterator iter(child->Document(), child->GetContent(),
328 nsGkAtoms::aria_owns);
329 parent = iter.Next();
330 tryOwnsParent = false;
333 if (ARIAMenubar != mActiveARIAMenubar) {
334 // Leaving ARIA menu. Fire menu_end event on current menubar.
335 if (mActiveARIAMenubar) {
336 nsRefPtr<AccEvent> menuEndEvent =
337 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
338 aEvent->FromUserInput());
339 nsEventShell::FireEvent(menuEndEvent);
342 mActiveARIAMenubar = ARIAMenubar;
344 // Entering ARIA menu. Fire menu_start event.
345 if (mActiveARIAMenubar) {
346 nsRefPtr<AccEvent> menuStartEvent =
347 new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
348 mActiveARIAMenubar, aEvent->FromUserInput());
349 nsEventShell::FireEvent(menuStartEvent);
352 } else if (mActiveARIAMenubar) {
353 // Focus left a menu. Fire menu_end event.
354 nsRefPtr<AccEvent> menuEndEvent =
355 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
356 aEvent->FromUserInput());
357 nsEventShell::FireEvent(menuEndEvent);
359 mActiveARIAMenubar = nullptr;
362 #ifdef A11Y_LOG
363 if (logging::IsEnabled(logging::eFocus))
364 logging::FocusNotificationTarget("fire focus event", "Target", target);
365 #endif
367 // Reset cached caret value. The cache will be updated upon processing the
368 // next caret move event. This ensures that we will return the correct caret
369 // offset before the caret move event is handled.
370 SelectionMgr()->ResetCaretOffset();
372 nsRefPtr<AccEvent> focusEvent =
373 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
374 nsEventShell::FireEvent(focusEvent);
376 // Fire scrolling_start event when the document receives the focus if it has
377 // an anchor jump. If an accessible within the document receive the focus
378 // then null out the anchor jump because it no longer applies.
379 DocAccessible* targetDocument = target->Document();
380 Accessible* anchorJump = targetDocument->AnchorJump();
381 if (anchorJump) {
382 if (target == targetDocument) {
383 // XXX: bug 625699, note in some cases the node could go away before we
384 // we receive focus event, for example if the node is removed from DOM.
385 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
386 anchorJump, aEvent->FromUserInput());
388 targetDocument->SetAnchorJump(nullptr);
392 nsINode*
393 FocusManager::FocusedDOMNode() const
395 nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
396 nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
398 // No focus on remote target elements like xul:browser having DOM focus and
399 // residing in chrome process because it means an element in content process
400 // keeps the focus.
401 if (focusedElm) {
402 if (EventStateManager::IsRemoteTarget(focusedElm)) {
403 return nullptr;
405 return focusedElm;
408 // Otherwise the focus can be on DOM document.
409 nsPIDOMWindow* focusedWnd = DOMFocusManager->GetFocusedWindow();
410 return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
413 nsIDocument*
414 FocusManager::FocusedDOMDocument() const
416 nsINode* focusedNode = FocusedDOMNode();
417 return focusedNode ? focusedNode->OwnerDoc() : nullptr;
420 } // namespace a11y
421 } // namespace mozilla