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 ***** */
37 :
38 : #include "FocusManager.h"
39 :
40 : #include "nsAccessibilityService.h"
41 : #include "nsAccUtils.h"
42 : #include "nsRootAccessible.h"
43 : #include "Role.h"
44 :
45 : #include "nsEventStateManager.h"
46 : #include "nsFocusManager.h"
47 :
48 : namespace dom = mozilla::dom;
49 : using namespace mozilla::a11y;
50 :
51 0 : FocusManager::FocusManager()
52 : {
53 0 : }
54 :
55 0 : FocusManager::~FocusManager()
56 : {
57 0 : }
58 :
59 : nsAccessible*
60 0 : FocusManager::FocusedAccessible() const
61 : {
62 0 : if (mActiveItem)
63 0 : return mActiveItem;
64 :
65 0 : nsINode* focusedNode = FocusedDOMNode();
66 0 : if (focusedNode) {
67 : nsDocAccessible* doc =
68 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
69 0 : return doc ? doc->GetAccessibleOrContainer(focusedNode) : nsnull;
70 : }
71 :
72 0 : return nsnull;
73 : }
74 :
75 : bool
76 0 : FocusManager::IsFocused(const nsAccessible* aAccessible) const
77 : {
78 0 : if (mActiveItem)
79 0 : return mActiveItem == aAccessible;
80 :
81 0 : nsINode* focusedNode = FocusedDOMNode();
82 0 : if (focusedNode) {
83 : // XXX: Before getting an accessible for node having a DOM focus make sure
84 : // they belong to the same document because it can trigger unwanted document
85 : // accessible creation for temporary about:blank document. Without this
86 : // peculiarity we would end up with plain implementation based on
87 : // FocusedAccessible() method call. Make sure this issue is fixed in
88 : // bug 638465.
89 0 : if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
90 : nsDocAccessible* doc =
91 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
92 : return aAccessible ==
93 0 : (doc ? doc->GetAccessibleOrContainer(focusedNode) : nsnull);
94 : }
95 : }
96 0 : return false;
97 : }
98 :
99 : bool
100 0 : FocusManager::IsFocusWithin(const nsAccessible* aContainer) const
101 : {
102 0 : nsAccessible* child = FocusedAccessible();
103 0 : while (child) {
104 0 : if (child == aContainer)
105 0 : return true;
106 :
107 0 : child = child->Parent();
108 : }
109 0 : return false;
110 : }
111 :
112 : FocusManager::FocusDisposition
113 0 : FocusManager::IsInOrContainsFocus(const nsAccessible* aAccessible) const
114 : {
115 0 : nsAccessible* focus = FocusedAccessible();
116 0 : if (!focus)
117 0 : return eNone;
118 :
119 : // If focused.
120 0 : if (focus == aAccessible)
121 0 : return eFocused;
122 :
123 : // If contains the focus.
124 0 : nsAccessible* child = focus->Parent();
125 0 : while (child) {
126 0 : if (child == aAccessible)
127 0 : return eContainsFocus;
128 :
129 0 : child = child->Parent();
130 : }
131 :
132 : // If contained by focus.
133 0 : child = aAccessible->Parent();
134 0 : while (child) {
135 0 : if (child == focus)
136 0 : return eContainedByFocus;
137 :
138 0 : child = child->Parent();
139 : }
140 :
141 0 : return eNone;
142 : }
143 :
144 : void
145 0 : FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
146 : {
147 : A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM focus", "DOM focus target",
148 : aTarget)
149 :
150 0 : mActiveItem = nsnull;
151 :
152 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
153 0 : if (targetNode) {
154 : nsDocAccessible* document =
155 0 : GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
156 0 : if (document) {
157 : // Set selection listener for focused element.
158 0 : if (targetNode->IsElement()) {
159 0 : nsRootAccessible* root = document->RootAccessible();
160 0 : nsCaretAccessible* caretAcc = root->GetCaretAccessible();
161 0 : caretAcc->SetControlSelectionListener(targetNode->AsElement());
162 : }
163 :
164 : document->HandleNotification<FocusManager, nsINode>
165 0 : (this, &FocusManager::ProcessDOMFocus, targetNode);
166 : }
167 : }
168 0 : }
169 :
170 : void
171 0 : FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
172 : {
173 : A11YDEBUG_FOCUS_NOTIFICATION_SUPPORTSTARGET("DOM blur", "DOM blur target",
174 : aTarget)
175 :
176 0 : mActiveItem = nsnull;
177 :
178 : // If DOM document stays focused then fire accessible focus event to process
179 : // the case when no element within this DOM document will be focused.
180 0 : nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
181 0 : if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
182 0 : nsIDocument* DOMDoc = targetNode->OwnerDoc();
183 : nsDocAccessible* document =
184 0 : GetAccService()->GetDocAccessible(DOMDoc);
185 0 : if (document) {
186 : document->HandleNotification<FocusManager, nsINode>
187 0 : (this, &FocusManager::ProcessDOMFocus, DOMDoc);
188 : }
189 : }
190 0 : }
191 :
192 : void
193 0 : FocusManager::ActiveItemChanged(nsAccessible* aItem, bool aCheckIfActive)
194 : {
195 : A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("active item changed",
196 : "Active item", aItem)
197 :
198 : // Nothing changed, happens for XUL trees and HTML selects.
199 0 : if (aItem && aItem == mActiveItem)
200 0 : return;
201 :
202 0 : mActiveItem = nsnull;
203 :
204 0 : if (aItem && aCheckIfActive) {
205 0 : nsAccessible* widget = aItem->ContainerWidget();
206 : A11YDEBUG_FOCUS_LOG_WIDGET("Active item widget", widget)
207 0 : if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
208 0 : return;
209 : }
210 0 : mActiveItem = aItem;
211 :
212 : // If active item is changed then fire accessible focus event on it, otherwise
213 : // if there's no an active item then fire focus event to accessible having
214 : // DOM focus.
215 0 : nsAccessible* target = FocusedAccessible();
216 0 : if (target)
217 0 : DispatchFocusEvent(target->Document(), target);
218 : }
219 :
220 : void
221 0 : FocusManager::ForceFocusEvent()
222 : {
223 0 : nsINode* focusedNode = FocusedDOMNode();
224 0 : if (focusedNode) {
225 : nsDocAccessible* document =
226 0 : GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
227 0 : if (document) {
228 : document->HandleNotification<FocusManager, nsINode>
229 0 : (this, &FocusManager::ProcessDOMFocus, focusedNode);
230 : }
231 : }
232 0 : }
233 :
234 : void
235 0 : FocusManager::DispatchFocusEvent(nsDocAccessible* aDocument,
236 : nsAccessible* aTarget)
237 : {
238 0 : NS_PRECONDITION(aDocument, "No document for focused accessible!");
239 0 : if (aDocument) {
240 : nsRefPtr<AccEvent> event =
241 : new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
242 0 : eAutoDetect, AccEvent::eCoalesceOfSameType);
243 0 : aDocument->FireDelayedAccessibleEvent(event);
244 :
245 : A11YDEBUG_FOCUS_LOG_ACCTARGET("Focus notification", aTarget)
246 : }
247 0 : }
248 :
249 : void
250 0 : FocusManager::ProcessDOMFocus(nsINode* aTarget)
251 : {
252 : A11YDEBUG_FOCUS_NOTIFICATION_DOMTARGET("Process DOM focus",
253 : "Notification target", aTarget)
254 :
255 : nsDocAccessible* document =
256 0 : GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
257 :
258 0 : nsAccessible* target = document->GetAccessibleOrContainer(aTarget);
259 0 : if (target && document) {
260 : // Check if still focused. Otherwise we can end up with storing the active
261 : // item for control that isn't focused anymore.
262 : nsAccessible* DOMFocus =
263 0 : document->GetAccessibleOrContainer(FocusedDOMNode());
264 0 : if (target != DOMFocus)
265 0 : return;
266 :
267 0 : nsAccessible* activeItem = target->CurrentItem();
268 0 : if (activeItem) {
269 0 : mActiveItem = activeItem;
270 0 : target = activeItem;
271 : }
272 :
273 0 : DispatchFocusEvent(document, target);
274 : }
275 : }
276 :
277 : void
278 0 : FocusManager::ProcessFocusEvent(AccEvent* aEvent)
279 : {
280 0 : NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
281 : "Focus event is expected!");
282 :
283 0 : EIsFromUserInput fromUserInputFlag = aEvent->IsFromUserInput() ?
284 0 : eFromUserInput : eNoUserInput;
285 :
286 : // Emit focus event if event target is the active item. Otherwise then check
287 : // if it's still focused and then update active item and emit focus event.
288 0 : nsAccessible* target = aEvent->GetAccessible();
289 0 : if (target != mActiveItem) {
290 :
291 : // Check if still focused. Otherwise we can end up with storing the active
292 : // item for control that isn't focused anymore.
293 0 : nsDocAccessible* document = aEvent->GetDocAccessible();
294 0 : nsAccessible* DOMFocus = document->GetAccessibleOrContainer(FocusedDOMNode());
295 :
296 0 : if (target != DOMFocus)
297 0 : return;
298 :
299 0 : nsAccessible* activeItem = target->CurrentItem();
300 0 : if (activeItem) {
301 0 : mActiveItem = activeItem;
302 0 : target = activeItem;
303 : }
304 : }
305 :
306 : // Fire menu start/end events for ARIA menus.
307 0 : if (target->ARIARole() == roles::MENUITEM) {
308 : // The focus was moved into menu.
309 : nsAccessible* ARIAMenubar =
310 0 : nsAccUtils::GetAncestorWithRole(target, roles::MENUBAR);
311 :
312 0 : if (ARIAMenubar != mActiveARIAMenubar) {
313 : // Leaving ARIA menu. Fire menu_end event on current menubar.
314 0 : if (mActiveARIAMenubar) {
315 : nsRefPtr<AccEvent> menuEndEvent =
316 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
317 0 : fromUserInputFlag);
318 0 : nsEventShell::FireEvent(menuEndEvent);
319 : }
320 :
321 0 : mActiveARIAMenubar = ARIAMenubar;
322 :
323 : // Entering ARIA menu. Fire menu_start event.
324 0 : if (mActiveARIAMenubar) {
325 : nsRefPtr<AccEvent> menuStartEvent =
326 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
327 0 : mActiveARIAMenubar, fromUserInputFlag);
328 0 : nsEventShell::FireEvent(menuStartEvent);
329 : }
330 : }
331 0 : } else if (mActiveARIAMenubar) {
332 : // Focus left a menu. Fire menu_end event.
333 : nsRefPtr<AccEvent> menuEndEvent =
334 : new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
335 0 : fromUserInputFlag);
336 0 : nsEventShell::FireEvent(menuEndEvent);
337 :
338 0 : mActiveARIAMenubar = nsnull;
339 : }
340 :
341 : A11YDEBUG_FOCUS_NOTIFICATION_ACCTARGET("FIRE FOCUS EVENT", "Focus target",
342 : target)
343 :
344 : nsRefPtr<AccEvent> focusEvent =
345 0 : new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, fromUserInputFlag);
346 0 : nsEventShell::FireEvent(focusEvent);
347 :
348 : // Fire scrolling_start event when the document receives the focus if it has
349 : // an anchor jump. If an accessible within the document receive the focus
350 : // then null out the anchor jump because it no longer applies.
351 0 : nsDocAccessible* targetDocument = target->Document();
352 0 : nsAccessible* anchorJump = targetDocument->AnchorJump();
353 0 : if (anchorJump) {
354 0 : if (target == targetDocument) {
355 : // XXX: bug 625699, note in some cases the node could go away before we
356 : // we receive focus event, for example if the node is removed from DOM.
357 : nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
358 0 : anchorJump, fromUserInputFlag);
359 : }
360 0 : targetDocument->SetAnchorJump(nsnull);
361 : }
362 : }
363 :
364 : nsINode*
365 0 : FocusManager::FocusedDOMNode() const
366 : {
367 0 : nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
368 0 : nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
369 :
370 : // No focus on remote target elements like xul:browser having DOM focus and
371 : // residing in chrome process because it means an element in content process
372 : // keeps the focus.
373 0 : if (focusedElm) {
374 0 : if (nsEventStateManager::IsRemoteTarget(focusedElm))
375 0 : return nsnull;
376 0 : return focusedElm;
377 : }
378 :
379 : // Otherwise the focus can be on DOM document.
380 0 : nsCOMPtr<nsIDOMWindow> focusedWnd;
381 0 : DOMFocusManager->GetFocusedWindow(getter_AddRefs(focusedWnd));
382 0 : if (focusedWnd) {
383 0 : nsCOMPtr<nsIDOMDocument> DOMDoc;
384 0 : focusedWnd->GetDocument(getter_AddRefs(DOMDoc));
385 0 : nsCOMPtr<nsIDocument> DOMDocNode(do_QueryInterface(DOMDoc));
386 0 : return DOMDocNode;
387 : }
388 0 : return nsnull;
389 : }
390 :
391 : nsIDocument*
392 0 : FocusManager::FocusedDOMDocument() const
393 : {
394 0 : nsINode* focusedNode = FocusedDOMNode();
395 0 : return focusedNode ? focusedNode->OwnerDoc() : nsnull;
396 : }
|