accessible/src/base/FocusManager.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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"
    13 #include "Role.h"
    15 #include "nsFocusManager.h"
    16 #include "mozilla/EventStateManager.h"
    17 #include "mozilla/dom/Element.h"
    19 namespace mozilla {
    20 namespace a11y {
    22 FocusManager::FocusManager()
    23 {
    24 }
    26 FocusManager::~FocusManager()
    27 {
    28 }
    30 Accessible*
    31 FocusManager::FocusedAccessible() const
    32 {
    33   if (mActiveItem)
    34     return mActiveItem;
    36   nsINode* focusedNode = FocusedDOMNode();
    37   if (focusedNode) {
    38     DocAccessible* doc = 
    39       GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
    40     return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
    41   }
    43   return nullptr;
    44 }
    46 bool
    47 FocusManager::IsFocused(const Accessible* aAccessible) const
    48 {
    49   if (mActiveItem)
    50     return mActiveItem == aAccessible;
    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 }
    70 bool
    71 FocusManager::IsFocusWithin(const Accessible* aContainer) const
    72 {
    73   Accessible* child = FocusedAccessible();
    74   while (child) {
    75     if (child == aContainer)
    76       return true;
    78     child = child->Parent();
    79   }
    80   return false;
    81 }
    83 FocusManager::FocusDisposition
    84 FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
    85 {
    86   Accessible* focus = FocusedAccessible();
    87   if (!focus)
    88     return eNone;
    90   // If focused.
    91   if (focus == aAccessible)
    92     return eFocused;
    94   // If contains the focus.
    95   Accessible* child = focus->Parent();
    96   while (child) {
    97     if (child == aAccessible)
    98       return eContainsFocus;
   100     child = child->Parent();
   101   }
   103   // If contained by focus.
   104   child = aAccessible->Parent();
   105   while (child) {
   106     if (child == focus)
   107       return eContainedByFocus;
   109     child = child->Parent();
   110   }
   112   return eNone;
   113 }
   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
   123   mActiveItem = nullptr;
   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());
   134       document->HandleNotification<FocusManager, nsINode>
   135         (this, &FocusManager::ProcessDOMFocus, targetNode);
   136     }
   137   }
   138 }
   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
   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);
   157     if (document) {
   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);
   164     }
   165   }
   166 }
   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
   176   // Nothing changed, happens for XUL trees and HTML selects.
   177   if (aItem && aItem == mActiveItem)
   178     return;
   180   mActiveItem = nullptr;
   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;
   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 }
   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 }
   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);
   226 #ifdef A11Y_LOG
   227     if (logging::IsEnabled(logging::eFocus))
   228       logging::FocusDispatched(aTarget);
   229 #endif
   230   }
   231 }
   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
   241   DocAccessible* document =
   242     GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
   243   if (!document)
   244     return;
   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;
   254     Accessible* DOMFocus =
   255       document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
   256     if (target != DOMFocus)
   257       return;
   259     Accessible* activeItem = target->CurrentItem();
   260     if (activeItem) {
   261       mActiveItem = activeItem;
   262       target = activeItem;
   263     }
   265     DispatchFocusEvent(document, target);
   266   }
   267 }
   269 void
   270 FocusManager::ProcessFocusEvent(AccEvent* aEvent)
   271 {
   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();
   284     if (!focusedNode)
   285       return;
   287     Accessible* DOMFocus =
   288       document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
   289     if (target != DOMFocus)
   290       return;
   292     Accessible* activeItem = target->CurrentItem();
   293     if (activeItem) {
   294       mActiveItem = activeItem;
   295       target = activeItem;
   296     }
   297   }
   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         }
   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       }
   323       // If no required context role then check aria-owns relation.
   324       if (!tryOwnsParent)
   325         break;
   327       RelatedAccIterator iter(child->Document(), child->GetContent(),
   328                               nsGkAtoms::aria_owns);
   329       parent = iter.Next();
   330       tryOwnsParent = false;
   331     }
   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       }
   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);
   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);
   359     mActiveARIAMenubar = nullptr;
   360   }
   362 #ifdef A11Y_LOG
   363   if (logging::IsEnabled(logging::eFocus))
   364     logging::FocusNotificationTarget("fire focus event", "Target", target);
   365 #endif
   367   nsRefPtr<AccEvent> focusEvent =
   368     new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
   369   nsEventShell::FireEvent(focusEvent);
   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 }
   387 nsINode*
   388 FocusManager::FocusedDOMNode() const
   389 {
   390   nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
   391   nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
   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   }
   403   // Otherwise the focus can be on DOM document.
   404   nsPIDOMWindow* focusedWnd = DOMFocusManager->GetFocusedWindow();
   405   return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
   406 }
   408 nsIDocument*
   409 FocusManager::FocusedDOMDocument() const
   410 {
   411   nsINode* focusedNode = FocusedDOMNode();
   412   return focusedNode ? focusedNode->OwnerDoc() : nullptr;
   413 }
   415 } // namespace a11y
   416 } // namespace mozilla

mercurial