1143929
[gecko.git] / 
blob11439299c6faa70c247264c85aae0558ec1f9292
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is mozilla.org code.
15  *
16  * The Initial Developer of the Original Code is
17  * Mozilla Foundation.
18  * Portions created by the Initial Developer are Copyright (C) 2011
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *  Alexander Surkov <surkov.alexander@gmail.com> (original author)
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
38 #include "FocusManager.h"
40 #include "nsAccessibilityService.h"
41 #include "nsAccUtils.h"
42 #include "nsRootAccessible.h"
44 #include "nsFocusManager.h"
46 namespace dom = mozilla::dom;
47 using namespace mozilla::a11y;
49 FocusManager::FocusManager()
53 FocusManager::~FocusManager()
57 nsAccessible*
58 FocusManager::FocusedAccessible() const
60   if (mActiveItem)
61     return mActiveItem;
63   nsINode* focusedNode = FocusedDOMNode();
64   if (focusedNode)
65     return GetAccService()->GetAccessibleOrContainer(focusedNode, nsnull);
67   return nsnull;
70 bool
71 FocusManager::IsFocused(const nsAccessible* aAccessible) const
73   if (mActiveItem)
74     return mActiveItem == aAccessible;
76   nsINode* focusedNode = FocusedDOMNode();
77   if (focusedNode) {
78     // XXX: Before getting an accessible for node having a DOM focus make sure
79     // they belong to the same document because it can trigger unwanted document
80     // accessible creation for temporary about:blank document. Without this
81     // peculiarity we would end up with plain implementation based on
82     // FocusedAccessible() method call. Make sure this issue is fixed in
83     // bug 638465.
84     if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
85       return aAccessible ==
86         GetAccService()->GetAccessibleOrContainer(focusedNode, nsnull);
87     }
88   }
89   return false;
92 bool
93 FocusManager::IsFocusWithin(const nsAccessible* aContainer) const
95   nsAccessible* child = FocusedAccessible();
96   while (child) {
97     if (child == aContainer)
98       return true;
100     child = child->Parent();
101   }
102   return false;
105 FocusManager::FocusDisposition
106 FocusManager::IsInOrContainsFocus(const nsAccessible* aAccessible) const
108   nsAccessible* focus = FocusedAccessible();
109   if (!focus)
110     return eNone;
112   // If focused.
113   if (focus == aAccessible)
114     return eFocused;
116   // If contains the focus.
117   nsAccessible* child = focus->Parent();
118   while (child) {
119     if (child == aAccessible)
120       return eContainsFocus;
122     child = child->Parent();
123   }
125   // If contained by focus.
126   child = aAccessible->Parent();
127   while (child) {
128     if (child == focus)
129       return eContainedByFocus;
131     child = child->Parent();
132   }
134   return eNone;
137 void
138 FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
140   A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM focus", "DOM focus target",
141                                               aTarget)
143   mActiveItem = nsnull;
145   nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
146   if (targetNode) {
147     nsDocAccessible* document =
148       GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
149     if (document) {
150       // Set selection listener for focused element.
151       if (targetNode->IsElement()) {
152         nsRootAccessible* root = document->RootAccessible();
153         nsCaretAccessible* caretAcc = root->GetCaretAccessible();
154         caretAcc->SetControlSelectionListener(targetNode->AsElement());
155       }
157       document->HandleNotification<FocusManager, nsINode>
158         (this, &FocusManager::ProcessDOMFocus, targetNode);
159     }
160   }
163 void
164 FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
166   A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM blur", "DOM blur target",
167                                               aTarget)
169   mActiveItem = nsnull;
171   // If DOM document stays focused then fire accessible focus event to process
172   // the case when no element within this DOM document will be focused.
173   nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
174   if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
175     nsIDocument* DOMDoc = targetNode->OwnerDoc();
176     nsDocAccessible* document =
177       GetAccService()->GetDocAccessible(DOMDoc);
178     if (document) {
179       document->HandleNotification<FocusManager, nsINode>
180         (this, &FocusManager::ProcessDOMFocus, DOMDoc);
181     }
182   }
185 void
186 FocusManager::ActiveItemChanged(nsAccessible* aItem, bool aCheckIfActive)
188   A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("active item changed",
189                                          "Active item", aItem)
191   // Nothing changed, happens for XUL trees and HTML selects.
192   if (aItem && aItem == mActiveItem)
193     return;
195   mActiveItem = nsnull;
197   if (aItem && aCheckIfActive) {
198     nsAccessible* widget = aItem->ContainerWidget();
199     A11YDEBUG_FOCUS_LOG_WIDGET("Active item widget", widget)
200     if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
201       return;
202   }
203   mActiveItem = aItem;
205   // If active item is changed then fire accessible focus event on it, otherwise
206   // if there's no an active item then fire focus event to accessible having
207   // DOM focus.
208   nsAccessible* target = FocusedAccessible();
209   if (target)
210     DispatchFocusEvent(target->GetDocAccessible(), target);
213 void
214 FocusManager::ForceFocusEvent()
216   nsINode* focusedNode = FocusedDOMNode();
217   if (focusedNode) {
218     nsDocAccessible* document =
219       GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
220     if (document) {
221       document->HandleNotification<FocusManager, nsINode>
222         (this, &FocusManager::ProcessDOMFocus, focusedNode);
223     }
224   }
227 void
228 FocusManager::DispatchFocusEvent(nsDocAccessible* aDocument,
229                                  nsAccessible* aTarget)
231   NS_PRECONDITION(aDocument, "No document for focused accessible!");
232   if (aDocument) {
233     nsRefPtr<AccEvent> event =
234       new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
235                    eAutoDetect, AccEvent::eCoalesceOfSameType);
236     aDocument->FireDelayedAccessibleEvent(event);
238     A11YDEBUG_FOCUS_LOG_ACCTARGET("Focus notification", aTarget)
239   }
242 void
243 FocusManager::ProcessDOMFocus(nsINode* aTarget)
245   A11YDEBUG_FOCUS_NOTIFICATION_DOMTARGET("Process DOM focus",
246                                          "Notification target", aTarget)
248   nsDocAccessible* document =
249     GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
251   nsAccessible* target = document->GetAccessibleOrContainer(aTarget);
252   if (target) {
253     // Check if still focused. Otherwise we can end up with storing the active
254     // item for control that isn't focused anymore.
255     nsAccessible* DOMFocus =
256       GetAccService()->GetAccessibleOrContainer(FocusedDOMNode(), nsnull);
257     if (target != DOMFocus)
258       return;
260     nsAccessible* activeItem = target->CurrentItem();
261     if (activeItem) {
262       mActiveItem = activeItem;
263       target = activeItem;
264     }
266     DispatchFocusEvent(document, target);
267   }
270 void
271 FocusManager::ProcessFocusEvent(AccEvent* aEvent)
273   NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
274                   "Focus event is expected!");
276   EIsFromUserInput fromUserInputFlag = aEvent->IsFromUserInput() ?
277     eFromUserInput : eNoUserInput;
279   // Emit focus event if event target is the active item. Otherwise then check
280   // if it's still focused and then update active item and emit focus event.
281   nsAccessible* target = aEvent->GetAccessible();
282   if (target != mActiveItem) {
283     // Check if still focused. Otherwise we can end up with storing the active
284     // item for control that isn't focused anymore.
285     nsAccessible* DOMFocus =
286       GetAccService()->GetAccessibleOrContainer(FocusedDOMNode(), nsnull);
287     if (target != DOMFocus)
288       return;
290     nsAccessible* activeItem = target->CurrentItem();
291     if (activeItem) {
292       mActiveItem = activeItem;
293       target = activeItem;
294     }
295   }
297   // Fire menu start/end events for ARIA menus.
298   if (target->ARIARole() == nsIAccessibleRole::ROLE_MENUITEM) {
299     // The focus was moved into menu.
300     nsAccessible* ARIAMenubar =
301       nsAccUtils::GetAncestorWithRole(target, nsIAccessibleRole::ROLE_MENUBAR);
303     if (ARIAMenubar != mActiveARIAMenubar) {
304       // Leaving ARIA menu. Fire menu_end event on current menubar.
305       if (mActiveARIAMenubar) {
306         nsRefPtr<AccEvent> menuEndEvent =
307           new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
308                        fromUserInputFlag);
309         nsEventShell::FireEvent(menuEndEvent);
310       }
312       mActiveARIAMenubar = ARIAMenubar;
314       // Entering ARIA menu. Fire menu_start event.
315       if (mActiveARIAMenubar) {
316         nsRefPtr<AccEvent> menuStartEvent =
317           new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
318                        mActiveARIAMenubar, fromUserInputFlag);
319         nsEventShell::FireEvent(menuStartEvent);
320       }
321     }
322   } else if (mActiveARIAMenubar) {
323     // Focus left a menu. Fire menu_end event.
324     nsRefPtr<AccEvent> menuEndEvent =
325       new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
326                    fromUserInputFlag);
327     nsEventShell::FireEvent(menuEndEvent);
329     mActiveARIAMenubar = nsnull;
330   }
332   A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("FIRE FOCUS EVENT", "Focus target",
333                                          target)
335   nsRefPtr<AccEvent> focusEvent =
336     new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, fromUserInputFlag);
337   nsEventShell::FireEvent(focusEvent);
339   // Fire scrolling_start event when the document receives the focus if it has
340   // an anchor jump. If an accessible within the document receive the focus
341   // then null out the anchor jump because it no longer applies.
342   nsDocAccessible* targetDocument = target->GetDocAccessible();
343   nsAccessible* anchorJump = targetDocument->AnchorJump();
344   if (anchorJump) {
345     if (target == targetDocument) {
346       // XXX: bug 625699, note in some cases the node could go away before we
347       // we receive focus event, for example if the node is removed from DOM.
348       nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
349                               anchorJump, fromUserInputFlag);
350     }
351     targetDocument->SetAnchorJump(nsnull);
352   }
355 nsIContent*
356 FocusManager::FocusedDOMElm() const
358   nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
359   return DOMFocusManager->GetFocusedContent();
362 nsIDocument*
363 FocusManager::FocusedDOMDocument() const
365   nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
367   nsCOMPtr<nsIDOMWindow> focusedWnd;
368   DOMFocusManager->GetFocusedWindow(getter_AddRefs(focusedWnd));
369   if (focusedWnd) {
370     nsCOMPtr<nsIDOMDocument> DOMDoc;
371     focusedWnd->GetDocument(getter_AddRefs(DOMDoc));
372     nsCOMPtr<nsIDocument> DOMDocNode(do_QueryInterface(DOMDoc));
373     return DOMDocNode;
374   }
375   return nsnull;