accessible/src/base/FocusManager.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "FocusManager.h"
michael@0 6
michael@0 7 #include "Accessible-inl.h"
michael@0 8 #include "AccIterator.h"
michael@0 9 #include "DocAccessible-inl.h"
michael@0 10 #include "nsAccessibilityService.h"
michael@0 11 #include "nsAccUtils.h"
michael@0 12 #include "nsEventShell.h"
michael@0 13 #include "Role.h"
michael@0 14
michael@0 15 #include "nsFocusManager.h"
michael@0 16 #include "mozilla/EventStateManager.h"
michael@0 17 #include "mozilla/dom/Element.h"
michael@0 18
michael@0 19 namespace mozilla {
michael@0 20 namespace a11y {
michael@0 21
michael@0 22 FocusManager::FocusManager()
michael@0 23 {
michael@0 24 }
michael@0 25
michael@0 26 FocusManager::~FocusManager()
michael@0 27 {
michael@0 28 }
michael@0 29
michael@0 30 Accessible*
michael@0 31 FocusManager::FocusedAccessible() const
michael@0 32 {
michael@0 33 if (mActiveItem)
michael@0 34 return mActiveItem;
michael@0 35
michael@0 36 nsINode* focusedNode = FocusedDOMNode();
michael@0 37 if (focusedNode) {
michael@0 38 DocAccessible* doc =
michael@0 39 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
michael@0 40 return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
michael@0 41 }
michael@0 42
michael@0 43 return nullptr;
michael@0 44 }
michael@0 45
michael@0 46 bool
michael@0 47 FocusManager::IsFocused(const Accessible* aAccessible) const
michael@0 48 {
michael@0 49 if (mActiveItem)
michael@0 50 return mActiveItem == aAccessible;
michael@0 51
michael@0 52 nsINode* focusedNode = FocusedDOMNode();
michael@0 53 if (focusedNode) {
michael@0 54 // XXX: Before getting an accessible for node having a DOM focus make sure
michael@0 55 // they belong to the same document because it can trigger unwanted document
michael@0 56 // accessible creation for temporary about:blank document. Without this
michael@0 57 // peculiarity we would end up with plain implementation based on
michael@0 58 // FocusedAccessible() method call. Make sure this issue is fixed in
michael@0 59 // bug 638465.
michael@0 60 if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
michael@0 61 DocAccessible* doc =
michael@0 62 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
michael@0 63 return aAccessible ==
michael@0 64 (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
michael@0 65 }
michael@0 66 }
michael@0 67 return false;
michael@0 68 }
michael@0 69
michael@0 70 bool
michael@0 71 FocusManager::IsFocusWithin(const Accessible* aContainer) const
michael@0 72 {
michael@0 73 Accessible* child = FocusedAccessible();
michael@0 74 while (child) {
michael@0 75 if (child == aContainer)
michael@0 76 return true;
michael@0 77
michael@0 78 child = child->Parent();
michael@0 79 }
michael@0 80 return false;
michael@0 81 }
michael@0 82
michael@0 83 FocusManager::FocusDisposition
michael@0 84 FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
michael@0 85 {
michael@0 86 Accessible* focus = FocusedAccessible();
michael@0 87 if (!focus)
michael@0 88 return eNone;
michael@0 89
michael@0 90 // If focused.
michael@0 91 if (focus == aAccessible)
michael@0 92 return eFocused;
michael@0 93
michael@0 94 // If contains the focus.
michael@0 95 Accessible* child = focus->Parent();
michael@0 96 while (child) {
michael@0 97 if (child == aAccessible)
michael@0 98 return eContainsFocus;
michael@0 99
michael@0 100 child = child->Parent();
michael@0 101 }
michael@0 102
michael@0 103 // If contained by focus.
michael@0 104 child = aAccessible->Parent();
michael@0 105 while (child) {
michael@0 106 if (child == focus)
michael@0 107 return eContainedByFocus;
michael@0 108
michael@0 109 child = child->Parent();
michael@0 110 }
michael@0 111
michael@0 112 return eNone;
michael@0 113 }
michael@0 114
michael@0 115 void
michael@0 116 FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
michael@0 117 {
michael@0 118 #ifdef A11Y_LOG
michael@0 119 if (logging::IsEnabled(logging::eFocus))
michael@0 120 logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
michael@0 121 #endif
michael@0 122
michael@0 123 mActiveItem = nullptr;
michael@0 124
michael@0 125 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
michael@0 126 if (targetNode) {
michael@0 127 DocAccessible* document =
michael@0 128 GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
michael@0 129 if (document) {
michael@0 130 // Set selection listener for focused element.
michael@0 131 if (targetNode->IsElement())
michael@0 132 SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
michael@0 133
michael@0 134 document->HandleNotification<FocusManager, nsINode>
michael@0 135 (this, &FocusManager::ProcessDOMFocus, targetNode);
michael@0 136 }
michael@0 137 }
michael@0 138 }
michael@0 139
michael@0 140 void
michael@0 141 FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
michael@0 142 {
michael@0 143 #ifdef A11Y_LOG
michael@0 144 if (logging::IsEnabled(logging::eFocus))
michael@0 145 logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
michael@0 146 #endif
michael@0 147
michael@0 148 mActiveItem = nullptr;
michael@0 149
michael@0 150 // If DOM document stays focused then fire accessible focus event to process
michael@0 151 // the case when no element within this DOM document will be focused.
michael@0 152 nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
michael@0 153 if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
michael@0 154 nsIDocument* DOMDoc = targetNode->OwnerDoc();
michael@0 155 DocAccessible* document =
michael@0 156 GetAccService()->GetDocAccessible(DOMDoc);
michael@0 157 if (document) {
michael@0 158 // Clear selection listener for previously focused element.
michael@0 159 if (targetNode->IsElement())
michael@0 160 SelectionMgr()->ClearControlSelectionListener();
michael@0 161
michael@0 162 document->HandleNotification<FocusManager, nsINode>
michael@0 163 (this, &FocusManager::ProcessDOMFocus, DOMDoc);
michael@0 164 }
michael@0 165 }
michael@0 166 }
michael@0 167
michael@0 168 void
michael@0 169 FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
michael@0 170 {
michael@0 171 #ifdef A11Y_LOG
michael@0 172 if (logging::IsEnabled(logging::eFocus))
michael@0 173 logging::FocusNotificationTarget("active item changed", "Item", aItem);
michael@0 174 #endif
michael@0 175
michael@0 176 // Nothing changed, happens for XUL trees and HTML selects.
michael@0 177 if (aItem && aItem == mActiveItem)
michael@0 178 return;
michael@0 179
michael@0 180 mActiveItem = nullptr;
michael@0 181
michael@0 182 if (aItem && aCheckIfActive) {
michael@0 183 Accessible* widget = aItem->ContainerWidget();
michael@0 184 #ifdef A11Y_LOG
michael@0 185 if (logging::IsEnabled(logging::eFocus))
michael@0 186 logging::ActiveWidget(widget);
michael@0 187 #endif
michael@0 188 if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
michael@0 189 return;
michael@0 190 }
michael@0 191 mActiveItem = aItem;
michael@0 192
michael@0 193 // If active item is changed then fire accessible focus event on it, otherwise
michael@0 194 // if there's no an active item then fire focus event to accessible having
michael@0 195 // DOM focus.
michael@0 196 Accessible* target = FocusedAccessible();
michael@0 197 if (target)
michael@0 198 DispatchFocusEvent(target->Document(), target);
michael@0 199 }
michael@0 200
michael@0 201 void
michael@0 202 FocusManager::ForceFocusEvent()
michael@0 203 {
michael@0 204 nsINode* focusedNode = FocusedDOMNode();
michael@0 205 if (focusedNode) {
michael@0 206 DocAccessible* document =
michael@0 207 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
michael@0 208 if (document) {
michael@0 209 document->HandleNotification<FocusManager, nsINode>
michael@0 210 (this, &FocusManager::ProcessDOMFocus, focusedNode);
michael@0 211 }
michael@0 212 }
michael@0 213 }
michael@0 214
michael@0 215 void
michael@0 216 FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
michael@0 217 Accessible* aTarget)
michael@0 218 {
michael@0 219 NS_PRECONDITION(aDocument, "No document for focused accessible!");
michael@0 220 if (aDocument) {
michael@0 221 nsRefPtr<AccEvent> event =
michael@0 222 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
michael@0 223 eAutoDetect, AccEvent::eCoalesceOfSameType);
michael@0 224 aDocument->FireDelayedEvent(event);
michael@0 225
michael@0 226 #ifdef A11Y_LOG
michael@0 227 if (logging::IsEnabled(logging::eFocus))
michael@0 228 logging::FocusDispatched(aTarget);
michael@0 229 #endif
michael@0 230 }
michael@0 231 }
michael@0 232
michael@0 233 void
michael@0 234 FocusManager::ProcessDOMFocus(nsINode* aTarget)
michael@0 235 {
michael@0 236 #ifdef A11Y_LOG
michael@0 237 if (logging::IsEnabled(logging::eFocus))
michael@0 238 logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
michael@0 239 #endif
michael@0 240
michael@0 241 DocAccessible* document =
michael@0 242 GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
michael@0 243 if (!document)
michael@0 244 return;
michael@0 245
michael@0 246 Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
michael@0 247 if (target) {
michael@0 248 // Check if still focused. Otherwise we can end up with storing the active
michael@0 249 // item for control that isn't focused anymore.
michael@0 250 nsINode* focusedNode = FocusedDOMNode();
michael@0 251 if (!focusedNode)
michael@0 252 return;
michael@0 253
michael@0 254 Accessible* DOMFocus =
michael@0 255 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
michael@0 256 if (target != DOMFocus)
michael@0 257 return;
michael@0 258
michael@0 259 Accessible* activeItem = target->CurrentItem();
michael@0 260 if (activeItem) {
michael@0 261 mActiveItem = activeItem;
michael@0 262 target = activeItem;
michael@0 263 }
michael@0 264
michael@0 265 DispatchFocusEvent(document, target);
michael@0 266 }
michael@0 267 }
michael@0 268
michael@0 269 void
michael@0 270 FocusManager::ProcessFocusEvent(AccEvent* aEvent)
michael@0 271 {
michael@0 272 NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
michael@0 273 "Focus event is expected!");
michael@0 274
michael@0 275 // Emit focus event if event target is the active item. Otherwise then check
michael@0 276 // if it's still focused and then update active item and emit focus event.
michael@0 277 Accessible* target = aEvent->GetAccessible();
michael@0 278 if (target != mActiveItem) {
michael@0 279
michael@0 280 // Check if still focused. Otherwise we can end up with storing the active
michael@0 281 // item for control that isn't focused anymore.
michael@0 282 DocAccessible* document = aEvent->GetDocAccessible();
michael@0 283 nsINode* focusedNode = FocusedDOMNode();
michael@0 284 if (!focusedNode)
michael@0 285 return;
michael@0 286
michael@0 287 Accessible* DOMFocus =
michael@0 288 document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
michael@0 289 if (target != DOMFocus)
michael@0 290 return;
michael@0 291
michael@0 292 Accessible* activeItem = target->CurrentItem();
michael@0 293 if (activeItem) {
michael@0 294 mActiveItem = activeItem;
michael@0 295 target = activeItem;
michael@0 296 }
michael@0 297 }
michael@0 298
michael@0 299 // Fire menu start/end events for ARIA menus.
michael@0 300 if (target->IsARIARole(nsGkAtoms::menuitem)) {
michael@0 301 // The focus was moved into menu.
michael@0 302 bool tryOwnsParent = true;
michael@0 303 Accessible* ARIAMenubar = nullptr;
michael@0 304 Accessible* child = target;
michael@0 305 Accessible* parent = child->Parent();
michael@0 306 while (parent) {
michael@0 307 nsRoleMapEntry* roleMap = parent->ARIARoleMap();
michael@0 308 if (roleMap) {
michael@0 309 if (roleMap->Is(nsGkAtoms::menubar)) {
michael@0 310 ARIAMenubar = parent;
michael@0 311 break;
michael@0 312 }
michael@0 313
michael@0 314 // Go up in the parent chain of the menu hierarchy.
michael@0 315 if (roleMap->Is(nsGkAtoms::menuitem) || roleMap->Is(nsGkAtoms::menu)) {
michael@0 316 child = parent;
michael@0 317 parent = child->Parent();
michael@0 318 tryOwnsParent = true;
michael@0 319 continue;
michael@0 320 }
michael@0 321 }
michael@0 322
michael@0 323 // If no required context role then check aria-owns relation.
michael@0 324 if (!tryOwnsParent)
michael@0 325 break;
michael@0 326
michael@0 327 RelatedAccIterator iter(child->Document(), child->GetContent(),
michael@0 328 nsGkAtoms::aria_owns);
michael@0 329 parent = iter.Next();
michael@0 330 tryOwnsParent = false;
michael@0 331 }
michael@0 332
michael@0 333 if (ARIAMenubar != mActiveARIAMenubar) {
michael@0 334 // Leaving ARIA menu. Fire menu_end event on current menubar.
michael@0 335 if (mActiveARIAMenubar) {
michael@0 336 nsRefPtr<AccEvent> menuEndEvent =
michael@0 337 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
michael@0 338 aEvent->FromUserInput());
michael@0 339 nsEventShell::FireEvent(menuEndEvent);
michael@0 340 }
michael@0 341
michael@0 342 mActiveARIAMenubar = ARIAMenubar;
michael@0 343
michael@0 344 // Entering ARIA menu. Fire menu_start event.
michael@0 345 if (mActiveARIAMenubar) {
michael@0 346 nsRefPtr<AccEvent> menuStartEvent =
michael@0 347 new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
michael@0 348 mActiveARIAMenubar, aEvent->FromUserInput());
michael@0 349 nsEventShell::FireEvent(menuStartEvent);
michael@0 350 }
michael@0 351 }
michael@0 352 } else if (mActiveARIAMenubar) {
michael@0 353 // Focus left a menu. Fire menu_end event.
michael@0 354 nsRefPtr<AccEvent> menuEndEvent =
michael@0 355 new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
michael@0 356 aEvent->FromUserInput());
michael@0 357 nsEventShell::FireEvent(menuEndEvent);
michael@0 358
michael@0 359 mActiveARIAMenubar = nullptr;
michael@0 360 }
michael@0 361
michael@0 362 #ifdef A11Y_LOG
michael@0 363 if (logging::IsEnabled(logging::eFocus))
michael@0 364 logging::FocusNotificationTarget("fire focus event", "Target", target);
michael@0 365 #endif
michael@0 366
michael@0 367 nsRefPtr<AccEvent> focusEvent =
michael@0 368 new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
michael@0 369 nsEventShell::FireEvent(focusEvent);
michael@0 370
michael@0 371 // Fire scrolling_start event when the document receives the focus if it has
michael@0 372 // an anchor jump. If an accessible within the document receive the focus
michael@0 373 // then null out the anchor jump because it no longer applies.
michael@0 374 DocAccessible* targetDocument = target->Document();
michael@0 375 Accessible* anchorJump = targetDocument->AnchorJump();
michael@0 376 if (anchorJump) {
michael@0 377 if (target == targetDocument) {
michael@0 378 // XXX: bug 625699, note in some cases the node could go away before we
michael@0 379 // we receive focus event, for example if the node is removed from DOM.
michael@0 380 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
michael@0 381 anchorJump, aEvent->FromUserInput());
michael@0 382 }
michael@0 383 targetDocument->SetAnchorJump(nullptr);
michael@0 384 }
michael@0 385 }
michael@0 386
michael@0 387 nsINode*
michael@0 388 FocusManager::FocusedDOMNode() const
michael@0 389 {
michael@0 390 nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
michael@0 391 nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
michael@0 392
michael@0 393 // No focus on remote target elements like xul:browser having DOM focus and
michael@0 394 // residing in chrome process because it means an element in content process
michael@0 395 // keeps the focus.
michael@0 396 if (focusedElm) {
michael@0 397 if (EventStateManager::IsRemoteTarget(focusedElm)) {
michael@0 398 return nullptr;
michael@0 399 }
michael@0 400 return focusedElm;
michael@0 401 }
michael@0 402
michael@0 403 // Otherwise the focus can be on DOM document.
michael@0 404 nsPIDOMWindow* focusedWnd = DOMFocusManager->GetFocusedWindow();
michael@0 405 return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
michael@0 406 }
michael@0 407
michael@0 408 nsIDocument*
michael@0 409 FocusManager::FocusedDOMDocument() const
michael@0 410 {
michael@0 411 nsINode* focusedNode = FocusedDOMNode();
michael@0 412 return focusedNode ? focusedNode->OwnerDoc() : nullptr;
michael@0 413 }
michael@0 414
michael@0 415 } // namespace a11y
michael@0 416 } // namespace mozilla

mercurial