accessible/src/base/FocusManager.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:f603589bfee2
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/. */
4
5 #include "FocusManager.h"
6
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"
14
15 #include "nsFocusManager.h"
16 #include "mozilla/EventStateManager.h"
17 #include "mozilla/dom/Element.h"
18
19 namespace mozilla {
20 namespace a11y {
21
22 FocusManager::FocusManager()
23 {
24 }
25
26 FocusManager::~FocusManager()
27 {
28 }
29
30 Accessible*
31 FocusManager::FocusedAccessible() const
32 {
33 if (mActiveItem)
34 return mActiveItem;
35
36 nsINode* focusedNode = FocusedDOMNode();
37 if (focusedNode) {
38 DocAccessible* doc =
39 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
40 return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
41 }
42
43 return nullptr;
44 }
45
46 bool
47 FocusManager::IsFocused(const Accessible* aAccessible) const
48 {
49 if (mActiveItem)
50 return mActiveItem == aAccessible;
51
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);
65 }
66 }
67 return false;
68 }
69
70 bool
71 FocusManager::IsFocusWithin(const Accessible* aContainer) const
72 {
73 Accessible* child = FocusedAccessible();
74 while (child) {
75 if (child == aContainer)
76 return true;
77
78 child = child->Parent();
79 }
80 return false;
81 }
82
83 FocusManager::FocusDisposition
84 FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
85 {
86 Accessible* focus = FocusedAccessible();
87 if (!focus)
88 return eNone;
89
90 // If focused.
91 if (focus == aAccessible)
92 return eFocused;
93
94 // If contains the focus.
95 Accessible* child = focus->Parent();
96 while (child) {
97 if (child == aAccessible)
98 return eContainsFocus;
99
100 child = child->Parent();
101 }
102
103 // If contained by focus.
104 child = aAccessible->Parent();
105 while (child) {
106 if (child == focus)
107 return eContainedByFocus;
108
109 child = child->Parent();
110 }
111
112 return eNone;
113 }
114
115 void
116 FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
117 {
118 #ifdef A11Y_LOG
119 if (logging::IsEnabled(logging::eFocus))
120 logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
121 #endif
122
123 mActiveItem = nullptr;
124
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());
133
134 document->HandleNotification<FocusManager, nsINode>
135 (this, &FocusManager::ProcessDOMFocus, targetNode);
136 }
137 }
138 }
139
140 void
141 FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
142 {
143 #ifdef A11Y_LOG
144 if (logging::IsEnabled(logging::eFocus))
145 logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
146 #endif
147
148 mActiveItem = nullptr;
149
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();
161
162 document->HandleNotification<FocusManager, nsINode>
163 (this, &FocusManager::ProcessDOMFocus, DOMDoc);
164 }
165 }
166 }
167
168 void
169 FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
170 {
171 #ifdef A11Y_LOG
172 if (logging::IsEnabled(logging::eFocus))
173 logging::FocusNotificationTarget("active item changed", "Item", aItem);
174 #endif
175
176 // Nothing changed, happens for XUL trees and HTML selects.
177 if (aItem && aItem == mActiveItem)
178 return;
179
180 mActiveItem = nullptr;
181
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;
190 }
191 mActiveItem = aItem;
192
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);
199 }
200
201 void
202 FocusManager::ForceFocusEvent()
203 {
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);
211 }
212 }
213 }
214
215 void
216 FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
217 Accessible* aTarget)
218 {
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);
225
226 #ifdef A11Y_LOG
227 if (logging::IsEnabled(logging::eFocus))
228 logging::FocusDispatched(aTarget);
229 #endif
230 }
231 }
232
233 void
234 FocusManager::ProcessDOMFocus(nsINode* aTarget)
235 {
236 #ifdef A11Y_LOG
237 if (logging::IsEnabled(logging::eFocus))
238 logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
239 #endif
240
241 DocAccessible* document =
242 GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
243 if (!document)
244 return;
245
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;
253
254 Accessible* DOMFocus =
255 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
256 if (target != DOMFocus)
257 return;
258
259 Accessible* activeItem = target->CurrentItem();
260 if (activeItem) {
261 mActiveItem = activeItem;
262 target = activeItem;
263 }
264
265 DispatchFocusEvent(document, target);
266 }
267 }
268
269 void
270 FocusManager::ProcessFocusEvent(AccEvent* aEvent)
271 {
272 NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
273 "Focus event is expected!");
274
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) {
279
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;
286
287 Accessible* DOMFocus =
288 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
289 if (target != DOMFocus)
290 return;
291
292 Accessible* activeItem = target->CurrentItem();
293 if (activeItem) {
294 mActiveItem = activeItem;
295 target = activeItem;
296 }
297 }
298
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;
312 }
313
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;
320 }
321 }
322
323 // If no required context role then check aria-owns relation.
324 if (!tryOwnsParent)
325 break;
326
327 RelatedAccIterator iter(child->Document(), child->GetContent(),
328 nsGkAtoms::aria_owns);
329 parent = iter.Next();
330 tryOwnsParent = false;
331 }
332
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);
340 }
341
342 mActiveARIAMenubar = ARIAMenubar;
343
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);
350 }
351 }
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);
358
359 mActiveARIAMenubar = nullptr;
360 }
361
362 #ifdef A11Y_LOG
363 if (logging::IsEnabled(logging::eFocus))
364 logging::FocusNotificationTarget("fire focus event", "Target", target);
365 #endif
366
367 nsRefPtr<AccEvent> focusEvent =
368 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
369 nsEventShell::FireEvent(focusEvent);
370
371 // Fire scrolling_start event when the document receives the focus if it has
372 // an anchor jump. If an accessible within the document receive the focus
373 // then null out the anchor jump because it no longer applies.
374 DocAccessible* targetDocument = target->Document();
375 Accessible* anchorJump = targetDocument->AnchorJump();
376 if (anchorJump) {
377 if (target == targetDocument) {
378 // XXX: bug 625699, note in some cases the node could go away before we
379 // we receive focus event, for example if the node is removed from DOM.
380 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
381 anchorJump, aEvent->FromUserInput());
382 }
383 targetDocument->SetAnchorJump(nullptr);
384 }
385 }
386
387 nsINode*
388 FocusManager::FocusedDOMNode() const
389 {
390 nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
391 nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
392
393 // No focus on remote target elements like xul:browser having DOM focus and
394 // residing in chrome process because it means an element in content process
395 // keeps the focus.
396 if (focusedElm) {
397 if (EventStateManager::IsRemoteTarget(focusedElm)) {
398 return nullptr;
399 }
400 return focusedElm;
401 }
402
403 // Otherwise the focus can be on DOM document.
404 nsPIDOMWindow* focusedWnd = DOMFocusManager->GetFocusedWindow();
405 return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
406 }
407
408 nsIDocument*
409 FocusManager::FocusedDOMDocument() const
410 {
411 nsINode* focusedNode = FocusedDOMNode();
412 return focusedNode ? focusedNode->OwnerDoc() : nullptr;
413 }
414
415 } // namespace a11y
416 } // namespace mozilla

mercurial