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"
15 #include "nsFocusManager.h"
16 #include "mozilla/EventStateManager.h"
17 #include "mozilla/dom/Element.h"
22 FocusManager::FocusManager()
26 FocusManager::~FocusManager()
31 FocusManager::FocusedAccessible() const
36 nsINode
* focusedNode
= FocusedDOMNode();
39 GetAccService()->GetDocAccessible(focusedNode
->OwnerDoc());
40 return doc
? doc
->GetAccessibleEvenIfNotInMapOrContainer(focusedNode
) : nullptr;
47 FocusManager::IsFocused(const Accessible
* aAccessible
) const
50 return mActiveItem
== aAccessible
;
52 nsINode
* focusedNode
= FocusedDOMNode();
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
60 if (focusedNode
->OwnerDoc() == aAccessible
->GetNode()->OwnerDoc()) {
62 GetAccService()->GetDocAccessible(focusedNode
->OwnerDoc());
64 (doc
? doc
->GetAccessibleEvenIfNotInMapOrContainer(focusedNode
) : nullptr);
71 FocusManager::IsFocusWithin(const Accessible
* aContainer
) const
73 Accessible
* child
= FocusedAccessible();
75 if (child
== aContainer
)
78 child
= child
->Parent();
83 FocusManager::FocusDisposition
84 FocusManager::IsInOrContainsFocus(const Accessible
* aAccessible
) const
86 Accessible
* focus
= FocusedAccessible();
91 if (focus
== aAccessible
)
94 // If contains the focus.
95 Accessible
* child
= focus
->Parent();
97 if (child
== aAccessible
)
98 return eContainsFocus
;
100 child
= child
->Parent();
103 // If contained by focus.
104 child
= aAccessible
->Parent();
107 return eContainedByFocus
;
109 child
= child
->Parent();
116 FocusManager::NotifyOfDOMFocus(nsISupports
* aTarget
)
119 if (logging::IsEnabled(logging::eFocus
))
120 logging::FocusNotificationTarget("DOM focus", "Target", aTarget
);
123 mActiveItem
= nullptr;
125 nsCOMPtr
<nsINode
> targetNode(do_QueryInterface(aTarget
));
127 DocAccessible
* document
=
128 GetAccService()->GetDocAccessible(targetNode
->OwnerDoc());
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
);
141 FocusManager::NotifyOfDOMBlur(nsISupports
* aTarget
)
144 if (logging::IsEnabled(logging::eFocus
))
145 logging::FocusNotificationTarget("DOM blur", "Target", aTarget
);
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
);
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
);
169 FocusManager::ActiveItemChanged(Accessible
* aItem
, bool aCheckIfActive
)
172 if (logging::IsEnabled(logging::eFocus
))
173 logging::FocusNotificationTarget("active item changed", "Item", aItem
);
176 // Nothing changed, happens for XUL trees and HTML selects.
177 if (aItem
&& aItem
== mActiveItem
)
180 mActiveItem
= nullptr;
182 if (aItem
&& aCheckIfActive
) {
183 Accessible
* widget
= aItem
->ContainerWidget();
185 if (logging::IsEnabled(logging::eFocus
))
186 logging::ActiveWidget(widget
);
188 if (!widget
|| !widget
->IsActiveWidget() || !widget
->AreItemsOperable())
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
196 Accessible
* target
= FocusedAccessible();
198 DispatchFocusEvent(target
->Document(), target
);
202 FocusManager::ForceFocusEvent()
204 nsINode
* focusedNode
= FocusedDOMNode();
206 DocAccessible
* document
=
207 GetAccService()->GetDocAccessible(focusedNode
->OwnerDoc());
209 document
->HandleNotification
<FocusManager
, nsINode
>
210 (this, &FocusManager::ProcessDOMFocus
, focusedNode
);
216 FocusManager::DispatchFocusEvent(DocAccessible
* aDocument
,
219 NS_PRECONDITION(aDocument
, "No document for focused accessible!");
221 nsRefPtr
<AccEvent
> event
=
222 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS
, aTarget
,
223 eAutoDetect
, AccEvent::eCoalesceOfSameType
);
224 aDocument
->FireDelayedEvent(event
);
227 if (logging::IsEnabled(logging::eFocus
))
228 logging::FocusDispatched(aTarget
);
234 FocusManager::ProcessDOMFocus(nsINode
* aTarget
)
237 if (logging::IsEnabled(logging::eFocus
))
238 logging::FocusNotificationTarget("process DOM focus", "Target", aTarget
);
241 DocAccessible
* document
=
242 GetAccService()->GetDocAccessible(aTarget
->OwnerDoc());
246 Accessible
* target
= document
->GetAccessibleEvenIfNotInMapOrContainer(aTarget
);
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();
254 Accessible
* DOMFocus
=
255 document
->GetAccessibleEvenIfNotInMapOrContainer(focusedNode
);
256 if (target
!= DOMFocus
)
259 Accessible
* activeItem
= target
->CurrentItem();
261 mActiveItem
= activeItem
;
265 DispatchFocusEvent(document
, target
);
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();
287 Accessible
* DOMFocus
=
288 document
->GetAccessibleEvenIfNotInMapOrContainer(focusedNode
);
289 if (target
!= DOMFocus
)
292 Accessible
* activeItem
= target
->CurrentItem();
294 mActiveItem
= 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();
307 nsRoleMapEntry
* roleMap
= parent
->ARIARoleMap();
309 if (roleMap
->Is(nsGkAtoms::menubar
)) {
310 ARIAMenubar
= parent
;
314 // Go up in the parent chain of the menu hierarchy.
315 if (roleMap
->Is(nsGkAtoms::menuitem
) || roleMap
->Is(nsGkAtoms::menu
)) {
317 parent
= child
->Parent();
318 tryOwnsParent
= true;
323 // If no required context role then check aria-owns relation.
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;
363 if (logging::IsEnabled(logging::eFocus
))
364 logging::FocusNotificationTarget("fire focus event", "Target", target
);
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();
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);
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
402 if (EventStateManager::IsRemoteTarget(focusedElm
)) {
408 // Otherwise the focus can be on DOM document.
409 nsPIDOMWindow
* focusedWnd
= DOMFocusManager
->GetFocusedWindow();
410 return focusedWnd
? focusedWnd
->GetExtantDoc() : nullptr;
414 FocusManager::FocusedDOMDocument() const
416 nsINode
* focusedNode
= FocusedDOMNode();
417 return focusedNode
? focusedNode
->OwnerDoc() : nullptr;
421 } // namespace mozilla