1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/base/FocusManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,416 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "FocusManager.h" 1.9 + 1.10 +#include "Accessible-inl.h" 1.11 +#include "AccIterator.h" 1.12 +#include "DocAccessible-inl.h" 1.13 +#include "nsAccessibilityService.h" 1.14 +#include "nsAccUtils.h" 1.15 +#include "nsEventShell.h" 1.16 +#include "Role.h" 1.17 + 1.18 +#include "nsFocusManager.h" 1.19 +#include "mozilla/EventStateManager.h" 1.20 +#include "mozilla/dom/Element.h" 1.21 + 1.22 +namespace mozilla { 1.23 +namespace a11y { 1.24 + 1.25 +FocusManager::FocusManager() 1.26 +{ 1.27 +} 1.28 + 1.29 +FocusManager::~FocusManager() 1.30 +{ 1.31 +} 1.32 + 1.33 +Accessible* 1.34 +FocusManager::FocusedAccessible() const 1.35 +{ 1.36 + if (mActiveItem) 1.37 + return mActiveItem; 1.38 + 1.39 + nsINode* focusedNode = FocusedDOMNode(); 1.40 + if (focusedNode) { 1.41 + DocAccessible* doc = 1.42 + GetAccService()->GetDocAccessible(focusedNode->OwnerDoc()); 1.43 + return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr; 1.44 + } 1.45 + 1.46 + return nullptr; 1.47 +} 1.48 + 1.49 +bool 1.50 +FocusManager::IsFocused(const Accessible* aAccessible) const 1.51 +{ 1.52 + if (mActiveItem) 1.53 + return mActiveItem == aAccessible; 1.54 + 1.55 + nsINode* focusedNode = FocusedDOMNode(); 1.56 + if (focusedNode) { 1.57 + // XXX: Before getting an accessible for node having a DOM focus make sure 1.58 + // they belong to the same document because it can trigger unwanted document 1.59 + // accessible creation for temporary about:blank document. Without this 1.60 + // peculiarity we would end up with plain implementation based on 1.61 + // FocusedAccessible() method call. Make sure this issue is fixed in 1.62 + // bug 638465. 1.63 + if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) { 1.64 + DocAccessible* doc = 1.65 + GetAccService()->GetDocAccessible(focusedNode->OwnerDoc()); 1.66 + return aAccessible == 1.67 + (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr); 1.68 + } 1.69 + } 1.70 + return false; 1.71 +} 1.72 + 1.73 +bool 1.74 +FocusManager::IsFocusWithin(const Accessible* aContainer) const 1.75 +{ 1.76 + Accessible* child = FocusedAccessible(); 1.77 + while (child) { 1.78 + if (child == aContainer) 1.79 + return true; 1.80 + 1.81 + child = child->Parent(); 1.82 + } 1.83 + return false; 1.84 +} 1.85 + 1.86 +FocusManager::FocusDisposition 1.87 +FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const 1.88 +{ 1.89 + Accessible* focus = FocusedAccessible(); 1.90 + if (!focus) 1.91 + return eNone; 1.92 + 1.93 + // If focused. 1.94 + if (focus == aAccessible) 1.95 + return eFocused; 1.96 + 1.97 + // If contains the focus. 1.98 + Accessible* child = focus->Parent(); 1.99 + while (child) { 1.100 + if (child == aAccessible) 1.101 + return eContainsFocus; 1.102 + 1.103 + child = child->Parent(); 1.104 + } 1.105 + 1.106 + // If contained by focus. 1.107 + child = aAccessible->Parent(); 1.108 + while (child) { 1.109 + if (child == focus) 1.110 + return eContainedByFocus; 1.111 + 1.112 + child = child->Parent(); 1.113 + } 1.114 + 1.115 + return eNone; 1.116 +} 1.117 + 1.118 +void 1.119 +FocusManager::NotifyOfDOMFocus(nsISupports* aTarget) 1.120 +{ 1.121 +#ifdef A11Y_LOG 1.122 + if (logging::IsEnabled(logging::eFocus)) 1.123 + logging::FocusNotificationTarget("DOM focus", "Target", aTarget); 1.124 +#endif 1.125 + 1.126 + mActiveItem = nullptr; 1.127 + 1.128 + nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget)); 1.129 + if (targetNode) { 1.130 + DocAccessible* document = 1.131 + GetAccService()->GetDocAccessible(targetNode->OwnerDoc()); 1.132 + if (document) { 1.133 + // Set selection listener for focused element. 1.134 + if (targetNode->IsElement()) 1.135 + SelectionMgr()->SetControlSelectionListener(targetNode->AsElement()); 1.136 + 1.137 + document->HandleNotification<FocusManager, nsINode> 1.138 + (this, &FocusManager::ProcessDOMFocus, targetNode); 1.139 + } 1.140 + } 1.141 +} 1.142 + 1.143 +void 1.144 +FocusManager::NotifyOfDOMBlur(nsISupports* aTarget) 1.145 +{ 1.146 +#ifdef A11Y_LOG 1.147 + if (logging::IsEnabled(logging::eFocus)) 1.148 + logging::FocusNotificationTarget("DOM blur", "Target", aTarget); 1.149 +#endif 1.150 + 1.151 + mActiveItem = nullptr; 1.152 + 1.153 + // If DOM document stays focused then fire accessible focus event to process 1.154 + // the case when no element within this DOM document will be focused. 1.155 + nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget)); 1.156 + if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) { 1.157 + nsIDocument* DOMDoc = targetNode->OwnerDoc(); 1.158 + DocAccessible* document = 1.159 + GetAccService()->GetDocAccessible(DOMDoc); 1.160 + if (document) { 1.161 + // Clear selection listener for previously focused element. 1.162 + if (targetNode->IsElement()) 1.163 + SelectionMgr()->ClearControlSelectionListener(); 1.164 + 1.165 + document->HandleNotification<FocusManager, nsINode> 1.166 + (this, &FocusManager::ProcessDOMFocus, DOMDoc); 1.167 + } 1.168 + } 1.169 +} 1.170 + 1.171 +void 1.172 +FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive) 1.173 +{ 1.174 +#ifdef A11Y_LOG 1.175 + if (logging::IsEnabled(logging::eFocus)) 1.176 + logging::FocusNotificationTarget("active item changed", "Item", aItem); 1.177 +#endif 1.178 + 1.179 + // Nothing changed, happens for XUL trees and HTML selects. 1.180 + if (aItem && aItem == mActiveItem) 1.181 + return; 1.182 + 1.183 + mActiveItem = nullptr; 1.184 + 1.185 + if (aItem && aCheckIfActive) { 1.186 + Accessible* widget = aItem->ContainerWidget(); 1.187 +#ifdef A11Y_LOG 1.188 + if (logging::IsEnabled(logging::eFocus)) 1.189 + logging::ActiveWidget(widget); 1.190 +#endif 1.191 + if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable()) 1.192 + return; 1.193 + } 1.194 + mActiveItem = aItem; 1.195 + 1.196 + // If active item is changed then fire accessible focus event on it, otherwise 1.197 + // if there's no an active item then fire focus event to accessible having 1.198 + // DOM focus. 1.199 + Accessible* target = FocusedAccessible(); 1.200 + if (target) 1.201 + DispatchFocusEvent(target->Document(), target); 1.202 +} 1.203 + 1.204 +void 1.205 +FocusManager::ForceFocusEvent() 1.206 +{ 1.207 + nsINode* focusedNode = FocusedDOMNode(); 1.208 + if (focusedNode) { 1.209 + DocAccessible* document = 1.210 + GetAccService()->GetDocAccessible(focusedNode->OwnerDoc()); 1.211 + if (document) { 1.212 + document->HandleNotification<FocusManager, nsINode> 1.213 + (this, &FocusManager::ProcessDOMFocus, focusedNode); 1.214 + } 1.215 + } 1.216 +} 1.217 + 1.218 +void 1.219 +FocusManager::DispatchFocusEvent(DocAccessible* aDocument, 1.220 + Accessible* aTarget) 1.221 +{ 1.222 + NS_PRECONDITION(aDocument, "No document for focused accessible!"); 1.223 + if (aDocument) { 1.224 + nsRefPtr<AccEvent> event = 1.225 + new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget, 1.226 + eAutoDetect, AccEvent::eCoalesceOfSameType); 1.227 + aDocument->FireDelayedEvent(event); 1.228 + 1.229 +#ifdef A11Y_LOG 1.230 + if (logging::IsEnabled(logging::eFocus)) 1.231 + logging::FocusDispatched(aTarget); 1.232 +#endif 1.233 + } 1.234 +} 1.235 + 1.236 +void 1.237 +FocusManager::ProcessDOMFocus(nsINode* aTarget) 1.238 +{ 1.239 +#ifdef A11Y_LOG 1.240 + if (logging::IsEnabled(logging::eFocus)) 1.241 + logging::FocusNotificationTarget("process DOM focus", "Target", aTarget); 1.242 +#endif 1.243 + 1.244 + DocAccessible* document = 1.245 + GetAccService()->GetDocAccessible(aTarget->OwnerDoc()); 1.246 + if (!document) 1.247 + return; 1.248 + 1.249 + Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget); 1.250 + if (target) { 1.251 + // Check if still focused. Otherwise we can end up with storing the active 1.252 + // item for control that isn't focused anymore. 1.253 + nsINode* focusedNode = FocusedDOMNode(); 1.254 + if (!focusedNode) 1.255 + return; 1.256 + 1.257 + Accessible* DOMFocus = 1.258 + document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode); 1.259 + if (target != DOMFocus) 1.260 + return; 1.261 + 1.262 + Accessible* activeItem = target->CurrentItem(); 1.263 + if (activeItem) { 1.264 + mActiveItem = activeItem; 1.265 + target = activeItem; 1.266 + } 1.267 + 1.268 + DispatchFocusEvent(document, target); 1.269 + } 1.270 +} 1.271 + 1.272 +void 1.273 +FocusManager::ProcessFocusEvent(AccEvent* aEvent) 1.274 +{ 1.275 + NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS, 1.276 + "Focus event is expected!"); 1.277 + 1.278 + // Emit focus event if event target is the active item. Otherwise then check 1.279 + // if it's still focused and then update active item and emit focus event. 1.280 + Accessible* target = aEvent->GetAccessible(); 1.281 + if (target != mActiveItem) { 1.282 + 1.283 + // Check if still focused. Otherwise we can end up with storing the active 1.284 + // item for control that isn't focused anymore. 1.285 + DocAccessible* document = aEvent->GetDocAccessible(); 1.286 + nsINode* focusedNode = FocusedDOMNode(); 1.287 + if (!focusedNode) 1.288 + return; 1.289 + 1.290 + Accessible* DOMFocus = 1.291 + document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode); 1.292 + if (target != DOMFocus) 1.293 + return; 1.294 + 1.295 + Accessible* activeItem = target->CurrentItem(); 1.296 + if (activeItem) { 1.297 + mActiveItem = activeItem; 1.298 + target = activeItem; 1.299 + } 1.300 + } 1.301 + 1.302 + // Fire menu start/end events for ARIA menus. 1.303 + if (target->IsARIARole(nsGkAtoms::menuitem)) { 1.304 + // The focus was moved into menu. 1.305 + bool tryOwnsParent = true; 1.306 + Accessible* ARIAMenubar = nullptr; 1.307 + Accessible* child = target; 1.308 + Accessible* parent = child->Parent(); 1.309 + while (parent) { 1.310 + nsRoleMapEntry* roleMap = parent->ARIARoleMap(); 1.311 + if (roleMap) { 1.312 + if (roleMap->Is(nsGkAtoms::menubar)) { 1.313 + ARIAMenubar = parent; 1.314 + break; 1.315 + } 1.316 + 1.317 + // Go up in the parent chain of the menu hierarchy. 1.318 + if (roleMap->Is(nsGkAtoms::menuitem) || roleMap->Is(nsGkAtoms::menu)) { 1.319 + child = parent; 1.320 + parent = child->Parent(); 1.321 + tryOwnsParent = true; 1.322 + continue; 1.323 + } 1.324 + } 1.325 + 1.326 + // If no required context role then check aria-owns relation. 1.327 + if (!tryOwnsParent) 1.328 + break; 1.329 + 1.330 + RelatedAccIterator iter(child->Document(), child->GetContent(), 1.331 + nsGkAtoms::aria_owns); 1.332 + parent = iter.Next(); 1.333 + tryOwnsParent = false; 1.334 + } 1.335 + 1.336 + if (ARIAMenubar != mActiveARIAMenubar) { 1.337 + // Leaving ARIA menu. Fire menu_end event on current menubar. 1.338 + if (mActiveARIAMenubar) { 1.339 + nsRefPtr<AccEvent> menuEndEvent = 1.340 + new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, 1.341 + aEvent->FromUserInput()); 1.342 + nsEventShell::FireEvent(menuEndEvent); 1.343 + } 1.344 + 1.345 + mActiveARIAMenubar = ARIAMenubar; 1.346 + 1.347 + // Entering ARIA menu. Fire menu_start event. 1.348 + if (mActiveARIAMenubar) { 1.349 + nsRefPtr<AccEvent> menuStartEvent = 1.350 + new AccEvent(nsIAccessibleEvent::EVENT_MENU_START, 1.351 + mActiveARIAMenubar, aEvent->FromUserInput()); 1.352 + nsEventShell::FireEvent(menuStartEvent); 1.353 + } 1.354 + } 1.355 + } else if (mActiveARIAMenubar) { 1.356 + // Focus left a menu. Fire menu_end event. 1.357 + nsRefPtr<AccEvent> menuEndEvent = 1.358 + new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar, 1.359 + aEvent->FromUserInput()); 1.360 + nsEventShell::FireEvent(menuEndEvent); 1.361 + 1.362 + mActiveARIAMenubar = nullptr; 1.363 + } 1.364 + 1.365 +#ifdef A11Y_LOG 1.366 + if (logging::IsEnabled(logging::eFocus)) 1.367 + logging::FocusNotificationTarget("fire focus event", "Target", target); 1.368 +#endif 1.369 + 1.370 + nsRefPtr<AccEvent> focusEvent = 1.371 + new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput()); 1.372 + nsEventShell::FireEvent(focusEvent); 1.373 + 1.374 + // Fire scrolling_start event when the document receives the focus if it has 1.375 + // an anchor jump. If an accessible within the document receive the focus 1.376 + // then null out the anchor jump because it no longer applies. 1.377 + DocAccessible* targetDocument = target->Document(); 1.378 + Accessible* anchorJump = targetDocument->AnchorJump(); 1.379 + if (anchorJump) { 1.380 + if (target == targetDocument) { 1.381 + // XXX: bug 625699, note in some cases the node could go away before we 1.382 + // we receive focus event, for example if the node is removed from DOM. 1.383 + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START, 1.384 + anchorJump, aEvent->FromUserInput()); 1.385 + } 1.386 + targetDocument->SetAnchorJump(nullptr); 1.387 + } 1.388 +} 1.389 + 1.390 +nsINode* 1.391 +FocusManager::FocusedDOMNode() const 1.392 +{ 1.393 + nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); 1.394 + nsIContent* focusedElm = DOMFocusManager->GetFocusedContent(); 1.395 + 1.396 + // No focus on remote target elements like xul:browser having DOM focus and 1.397 + // residing in chrome process because it means an element in content process 1.398 + // keeps the focus. 1.399 + if (focusedElm) { 1.400 + if (EventStateManager::IsRemoteTarget(focusedElm)) { 1.401 + return nullptr; 1.402 + } 1.403 + return focusedElm; 1.404 + } 1.405 + 1.406 + // Otherwise the focus can be on DOM document. 1.407 + nsPIDOMWindow* focusedWnd = DOMFocusManager->GetFocusedWindow(); 1.408 + return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr; 1.409 +} 1.410 + 1.411 +nsIDocument* 1.412 +FocusManager::FocusedDOMDocument() const 1.413 +{ 1.414 + nsINode* focusedNode = FocusedDOMNode(); 1.415 + return focusedNode ? focusedNode->OwnerDoc() : nullptr; 1.416 +} 1.417 + 1.418 +} // namespace a11y 1.419 +} // namespace mozilla