Line data Source code
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 : #include "mozilla/dom/TabParent.h"
19 :
20 : namespace mozilla {
21 : namespace a11y {
22 :
23 0 : FocusManager::FocusManager()
24 : {
25 0 : }
26 :
27 0 : FocusManager::~FocusManager()
28 : {
29 0 : }
30 :
31 : Accessible*
32 0 : FocusManager::FocusedAccessible() const
33 : {
34 0 : if (mActiveItem)
35 0 : return mActiveItem;
36 :
37 0 : nsINode* focusedNode = FocusedDOMNode();
38 0 : if (focusedNode) {
39 : DocAccessible* doc =
40 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
41 0 : return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
42 : }
43 :
44 0 : return nullptr;
45 : }
46 :
47 : bool
48 0 : FocusManager::IsFocused(const Accessible* aAccessible) const
49 : {
50 0 : if (mActiveItem)
51 0 : return mActiveItem == aAccessible;
52 :
53 0 : nsINode* focusedNode = FocusedDOMNode();
54 0 : if (focusedNode) {
55 : // XXX: Before getting an accessible for node having a DOM focus make sure
56 : // they belong to the same document because it can trigger unwanted document
57 : // accessible creation for temporary about:blank document. Without this
58 : // peculiarity we would end up with plain implementation based on
59 : // FocusedAccessible() method call. Make sure this issue is fixed in
60 : // bug 638465.
61 0 : if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
62 : DocAccessible* doc =
63 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
64 : return aAccessible ==
65 0 : (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
66 : }
67 : }
68 0 : return false;
69 : }
70 :
71 : bool
72 0 : FocusManager::IsFocusWithin(const Accessible* aContainer) const
73 : {
74 0 : Accessible* child = FocusedAccessible();
75 0 : while (child) {
76 0 : if (child == aContainer)
77 0 : return true;
78 :
79 0 : child = child->Parent();
80 : }
81 0 : return false;
82 : }
83 :
84 : FocusManager::FocusDisposition
85 0 : FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
86 : {
87 0 : Accessible* focus = FocusedAccessible();
88 0 : if (!focus)
89 0 : return eNone;
90 :
91 : // If focused.
92 0 : if (focus == aAccessible)
93 0 : return eFocused;
94 :
95 : // If contains the focus.
96 0 : Accessible* child = focus->Parent();
97 0 : while (child) {
98 0 : if (child == aAccessible)
99 0 : return eContainsFocus;
100 :
101 0 : child = child->Parent();
102 : }
103 :
104 : // If contained by focus.
105 0 : child = aAccessible->Parent();
106 0 : while (child) {
107 0 : if (child == focus)
108 0 : return eContainedByFocus;
109 :
110 0 : child = child->Parent();
111 : }
112 :
113 0 : return eNone;
114 : }
115 :
116 : void
117 0 : FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
118 : {
119 : #ifdef A11Y_LOG
120 0 : if (logging::IsEnabled(logging::eFocus))
121 0 : logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
122 : #endif
123 :
124 0 : mActiveItem = nullptr;
125 :
126 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
127 0 : if (targetNode) {
128 : DocAccessible* document =
129 0 : GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
130 0 : if (document) {
131 : // Set selection listener for focused element.
132 0 : if (targetNode->IsElement())
133 0 : SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
134 :
135 : document->HandleNotification<FocusManager, nsINode>
136 0 : (this, &FocusManager::ProcessDOMFocus, targetNode);
137 : }
138 : }
139 0 : }
140 :
141 : void
142 0 : FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
143 : {
144 : #ifdef A11Y_LOG
145 0 : if (logging::IsEnabled(logging::eFocus))
146 0 : logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
147 : #endif
148 :
149 0 : mActiveItem = nullptr;
150 :
151 : // If DOM document stays focused then fire accessible focus event to process
152 : // the case when no element within this DOM document will be focused.
153 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
154 0 : if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
155 0 : nsIDocument* DOMDoc = targetNode->OwnerDoc();
156 : DocAccessible* document =
157 0 : GetAccService()->GetDocAccessible(DOMDoc);
158 0 : if (document) {
159 : // Clear selection listener for previously focused element.
160 0 : if (targetNode->IsElement())
161 0 : SelectionMgr()->ClearControlSelectionListener();
162 :
163 : document->HandleNotification<FocusManager, nsINode>
164 0 : (this, &FocusManager::ProcessDOMFocus, DOMDoc);
165 : }
166 : }
167 0 : }
168 :
169 : void
170 0 : FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
171 : {
172 : #ifdef A11Y_LOG
173 0 : if (logging::IsEnabled(logging::eFocus))
174 0 : logging::FocusNotificationTarget("active item changed", "Item", aItem);
175 : #endif
176 :
177 : // Nothing changed, happens for XUL trees and HTML selects.
178 0 : if (aItem && aItem == mActiveItem)
179 0 : return;
180 :
181 0 : mActiveItem = nullptr;
182 :
183 0 : if (aItem && aCheckIfActive) {
184 0 : Accessible* widget = aItem->ContainerWidget();
185 : #ifdef A11Y_LOG
186 0 : if (logging::IsEnabled(logging::eFocus))
187 0 : logging::ActiveWidget(widget);
188 : #endif
189 0 : if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
190 0 : return;
191 : }
192 0 : mActiveItem = aItem;
193 :
194 : // If mActiveItem is null we may need to shift a11y focus back to a tab
195 : // document. For example, when combobox popup is closed, then
196 : // the focus should be moved back to the combobox.
197 0 : if (!mActiveItem && XRE_IsParentProcess()) {
198 0 : nsFocusManager* domfm = nsFocusManager::GetFocusManager();
199 0 : if (domfm) {
200 0 : nsIContent* focusedElm = domfm->GetFocusedContent();
201 0 : if (EventStateManager::IsRemoteTarget(focusedElm)) {
202 0 : dom::TabParent* tab = dom::TabParent::GetFrom(focusedElm);
203 0 : if (tab) {
204 0 : a11y::DocAccessibleParent* dap = tab->GetTopLevelDocAccessible();
205 0 : if (dap) {
206 0 : Unused << dap->SendRestoreFocus();
207 : }
208 : }
209 : }
210 : }
211 : }
212 :
213 : // If active item is changed then fire accessible focus event on it, otherwise
214 : // if there's no an active item then fire focus event to accessible having
215 : // DOM focus.
216 0 : Accessible* target = FocusedAccessible();
217 0 : if (target) {
218 0 : DispatchFocusEvent(target->Document(), target);
219 : }
220 : }
221 :
222 : void
223 0 : FocusManager::ForceFocusEvent()
224 : {
225 0 : nsINode* focusedNode = FocusedDOMNode();
226 0 : if (focusedNode) {
227 : DocAccessible* document =
228 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
229 0 : if (document) {
230 : document->HandleNotification<FocusManager, nsINode>
231 0 : (this, &FocusManager::ProcessDOMFocus, focusedNode);
232 : }
233 : }
234 0 : }
235 :
236 : void
237 0 : FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
238 : Accessible* aTarget)
239 : {
240 0 : NS_PRECONDITION(aDocument, "No document for focused accessible!");
241 0 : if (aDocument) {
242 : RefPtr<AccEvent> event =
243 : new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
244 0 : eAutoDetect, AccEvent::eCoalesceOfSameType);
245 0 : aDocument->FireDelayedEvent(event);
246 :
247 : #ifdef A11Y_LOG
248 0 : if (logging::IsEnabled(logging::eFocus))
249 0 : logging::FocusDispatched(aTarget);
250 : #endif
251 : }
252 0 : }
253 :
254 : void
255 0 : FocusManager::ProcessDOMFocus(nsINode* aTarget)
256 : {
257 : #ifdef A11Y_LOG
258 0 : if (logging::IsEnabled(logging::eFocus))
259 0 : logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
260 : #endif
261 :
262 : DocAccessible* document =
263 0 : GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
264 0 : if (!document)
265 0 : return;
266 :
267 0 : Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
268 0 : if (target) {
269 : // Check if still focused. Otherwise we can end up with storing the active
270 : // item for control that isn't focused anymore.
271 0 : nsINode* focusedNode = FocusedDOMNode();
272 0 : if (!focusedNode)
273 0 : return;
274 :
275 : Accessible* DOMFocus =
276 0 : document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
277 0 : if (target != DOMFocus)
278 0 : return;
279 :
280 0 : Accessible* activeItem = target->CurrentItem();
281 0 : if (activeItem) {
282 0 : mActiveItem = activeItem;
283 0 : target = activeItem;
284 : }
285 :
286 0 : DispatchFocusEvent(document, target);
287 : }
288 : }
289 :
290 : void
291 0 : FocusManager::ProcessFocusEvent(AccEvent* aEvent)
292 : {
293 0 : NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
294 : "Focus event is expected!");
295 :
296 : // Emit focus event if event target is the active item. Otherwise then check
297 : // if it's still focused and then update active item and emit focus event.
298 0 : Accessible* target = aEvent->GetAccessible();
299 0 : if (target != mActiveItem) {
300 :
301 : // Check if still focused. Otherwise we can end up with storing the active
302 : // item for control that isn't focused anymore.
303 0 : DocAccessible* document = aEvent->Document();
304 0 : nsINode* focusedNode = FocusedDOMNode();
305 0 : if (!focusedNode)
306 0 : return;
307 :
308 : Accessible* DOMFocus =
309 0 : document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
310 0 : if (target != DOMFocus)
311 0 : return;
312 :
313 0 : Accessible* activeItem = target->CurrentItem();
314 0 : if (activeItem) {
315 0 : mActiveItem = activeItem;
316 0 : target = activeItem;
317 : }
318 : }
319 :
320 : // Fire menu start/end events for ARIA menus.
321 0 : if (target->IsARIARole(nsGkAtoms::menuitem)) {
322 : // The focus was moved into menu.
323 0 : Accessible* ARIAMenubar = nullptr;
324 0 : for (Accessible* parent = target->Parent(); parent; parent = parent->Parent()) {
325 0 : if (parent->IsARIARole(nsGkAtoms::menubar)) {
326 0 : ARIAMenubar = parent;
327 0 : break;
328 : }
329 :
330 : // Go up in the parent chain of the menu hierarchy.
331 0 : if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
332 0 : !parent->IsARIARole(nsGkAtoms::menu)) {
333 0 : break;
334 : }
335 : }
336 :
337 0 : if (ARIAMenubar != mActiveARIAMenubar) {
338 : // Leaving ARIA menu. Fire menu_end event on current menubar.
339 0 : if (mActiveARIAMenubar) {
340 : RefPtr<AccEvent> menuEndEvent =
341 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
342 0 : aEvent->FromUserInput());
343 0 : nsEventShell::FireEvent(menuEndEvent);
344 : }
345 :
346 0 : mActiveARIAMenubar = ARIAMenubar;
347 :
348 : // Entering ARIA menu. Fire menu_start event.
349 0 : if (mActiveARIAMenubar) {
350 : RefPtr<AccEvent> menuStartEvent =
351 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
352 0 : mActiveARIAMenubar, aEvent->FromUserInput());
353 0 : nsEventShell::FireEvent(menuStartEvent);
354 : }
355 : }
356 0 : } else if (mActiveARIAMenubar) {
357 : // Focus left a menu. Fire menu_end event.
358 : RefPtr<AccEvent> menuEndEvent =
359 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
360 0 : aEvent->FromUserInput());
361 0 : nsEventShell::FireEvent(menuEndEvent);
362 :
363 0 : mActiveARIAMenubar = nullptr;
364 : }
365 :
366 : #ifdef A11Y_LOG
367 0 : if (logging::IsEnabled(logging::eFocus))
368 0 : logging::FocusNotificationTarget("fire focus event", "Target", target);
369 : #endif
370 :
371 : // Reset cached caret value. The cache will be updated upon processing the
372 : // next caret move event. This ensures that we will return the correct caret
373 : // offset before the caret move event is handled.
374 0 : SelectionMgr()->ResetCaretOffset();
375 :
376 : RefPtr<AccEvent> focusEvent =
377 0 : new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
378 0 : nsEventShell::FireEvent(focusEvent);
379 :
380 : // Fire scrolling_start event when the document receives the focus if it has
381 : // an anchor jump. If an accessible within the document receive the focus
382 : // then null out the anchor jump because it no longer applies.
383 0 : DocAccessible* targetDocument = target->Document();
384 0 : Accessible* anchorJump = targetDocument->AnchorJump();
385 0 : if (anchorJump) {
386 0 : if (target == targetDocument) {
387 : // XXX: bug 625699, note in some cases the node could go away before we
388 : // we receive focus event, for example if the node is removed from DOM.
389 0 : nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
390 0 : anchorJump, aEvent->FromUserInput());
391 : }
392 0 : targetDocument->SetAnchorJump(nullptr);
393 : }
394 : }
395 :
396 : nsINode*
397 0 : FocusManager::FocusedDOMNode() const
398 : {
399 0 : nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
400 0 : nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
401 :
402 : // No focus on remote target elements like xul:browser having DOM focus and
403 : // residing in chrome process because it means an element in content process
404 : // keeps the focus.
405 0 : if (focusedElm) {
406 0 : if (EventStateManager::IsRemoteTarget(focusedElm)) {
407 0 : return nullptr;
408 : }
409 0 : return focusedElm;
410 : }
411 :
412 : // Otherwise the focus can be on DOM document.
413 0 : nsPIDOMWindowOuter* focusedWnd = DOMFocusManager->GetFocusedWindow();
414 0 : return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
415 : }
416 :
417 : nsIDocument*
418 0 : FocusManager::FocusedDOMDocument() const
419 : {
420 0 : nsINode* focusedNode = FocusedDOMNode();
421 0 : return focusedNode ? focusedNode->OwnerDoc() : nullptr;
422 : }
423 :
424 : } // namespace a11y
425 : } // namespace mozilla
|