RootAccessible.cpp (26167B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "RootAccessible.h" 7 8 #include "nsXULPopupManager.h" 9 10 #define CreateEvent CreateEventA 11 12 #include "LocalAccessible-inl.h" 13 #include "DocAccessible-inl.h" 14 #include "mozilla/a11y/DocAccessibleParent.h" 15 #include "nsAccessibilityService.h" 16 #include "nsAccUtils.h" 17 #include "nsCoreUtils.h" 18 #include "nsEventShell.h" 19 #include "Relation.h" 20 #include "mozilla/a11y/Role.h" 21 #include "States.h" 22 #include "XULTreeAccessible.h" 23 24 #include "mozilla/dom/BindingUtils.h" 25 #include "mozilla/dom/CustomEvent.h" 26 #include "mozilla/dom/Element.h" 27 #include "mozilla/dom/ScriptSettings.h" 28 #include "mozilla/dom/BrowserHost.h" 29 #include "mozilla/dom/VisualViewport.h" 30 31 #include "nsIDocShellTreeOwner.h" 32 #include "mozilla/dom/Event.h" 33 #include "mozilla/dom/EventTarget.h" 34 #include "nsGlobalWindowInner.h" 35 #include "nsIDOMXULMultSelectCntrlEl.h" 36 #include "mozilla/dom/Document.h" 37 #include "nsIInterfaceRequestorUtils.h" 38 #include "nsIPropertyBag2.h" 39 #include "nsPIDOMWindow.h" 40 #include "nsIWebBrowserChrome.h" 41 #include "nsFocusManager.h" 42 43 #include "nsIAppWindow.h" 44 45 using namespace mozilla; 46 using namespace mozilla::a11y; 47 using namespace mozilla::dom; 48 49 //////////////////////////////////////////////////////////////////////////////// 50 // nsISupports 51 52 NS_IMPL_ISUPPORTS_INHERITED(RootAccessible, DocAccessible, nsIDOMEventListener) 53 54 //////////////////////////////////////////////////////////////////////////////// 55 // Constructor/destructor 56 57 RootAccessible::RootAccessible(Document* aDocument, PresShell* aPresShell) 58 : DocAccessibleWrap(aDocument, aPresShell) { 59 mType = eRootType; 60 } 61 62 RootAccessible::~RootAccessible() {} 63 64 //////////////////////////////////////////////////////////////////////////////// 65 // LocalAccessible 66 67 ENameValueFlag RootAccessible::DirectName(nsString& aName) const { 68 aName.Truncate(); 69 70 if (ARIARoleMap()) { 71 LocalAccessible::DirectName(aName); 72 if (!aName.IsEmpty()) return eNameOK; 73 } 74 75 mDocumentNode->GetTitle(aName); 76 return eNameOK; 77 } 78 79 // RootAccessible protected member 80 uint32_t RootAccessible::GetChromeFlags() const { 81 // Return the flag set for the top level window as defined 82 // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME] 83 // Not simple: nsIAppWindow is not just a QI from nsIDOMWindow 84 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); 85 NS_ENSURE_TRUE(docShell, 0); 86 nsCOMPtr<nsIDocShellTreeOwner> treeOwner; 87 docShell->GetTreeOwner(getter_AddRefs(treeOwner)); 88 NS_ENSURE_TRUE(treeOwner, 0); 89 nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(treeOwner)); 90 if (!appWin) { 91 return 0; 92 } 93 uint32_t chromeFlags; 94 appWin->GetChromeFlags(&chromeFlags); 95 return chromeFlags; 96 } 97 98 uint64_t RootAccessible::NativeState() const { 99 uint64_t state = DocAccessibleWrap::NativeState(); 100 if (state & states::DEFUNCT) return state; 101 102 uint32_t chromeFlags = GetChromeFlags(); 103 if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) { 104 state |= states::SIZEABLE; 105 } 106 // If it has a titlebar it's movable 107 // XXX unless it's minimized or maximized, but not sure 108 // how to detect that 109 if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) { 110 state |= states::MOVEABLE; 111 } 112 if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) state |= states::MODAL; 113 114 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 115 if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow()) { 116 state |= states::ACTIVE; 117 } 118 119 return state; 120 } 121 122 const char* const kEventTypes[] = { 123 #ifdef DEBUG_DRAGDROPSTART 124 // Capture mouse over events and fire fake DRAGDROPSTART event to simplify 125 // debugging a11y objects with event viewers. 126 "mouseover", 127 #endif 128 // Fired when list or tree selection changes. 129 "select", 130 // Fired when value changes immediately, wether or not focused changed. 131 "ValueChange", "AlertActive", "TreeRowCountChanged", "TreeInvalidated", 132 // add ourself as a OpenStateChange listener (custom event fired in 133 // tree.xml) 134 "OpenStateChange", 135 // add ourself as a CheckboxStateChange listener (custom event fired in 136 // HTMLInputElement.cpp) 137 "CheckboxStateChange", 138 // add ourself as a RadioStateChange Listener (custom event fired in in 139 // HTMLInputElement.cpp & radio.js) 140 "RadioStateChange", "popupshown", "popuphiding", "DOMMenuInactive", 141 "DOMMenuItemActive", "DOMMenuItemInactive", "DOMMenuBarActive", 142 "DOMMenuBarInactive", "scroll", "DOMTitleChanged"}; 143 144 nsresult RootAccessible::AddEventListeners() { 145 // EventTarget interface allows to register event listeners to 146 // receive untrusted events (synthetic events generated by untrusted code). 147 // For example, XBL bindings implementations for elements that are hosted in 148 // non chrome document fire untrusted events. 149 // We must use the window's parent target in order to receive events from 150 // iframes and shadow DOM; e.g. ValueChange events from a <select> in an 151 // iframe or shadow DOM. The root document itself doesn't receive these. 152 nsPIDOMWindowOuter* window = mDocumentNode->GetWindow(); 153 nsCOMPtr<EventTarget> nstarget = window ? window->GetParentTarget() : nullptr; 154 155 if (nstarget) { 156 for (const char *const *e = kEventTypes, *const *e_end = 157 std::end(kEventTypes); 158 e < e_end; ++e) { 159 nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), this, 160 true, true); 161 NS_ENSURE_SUCCESS(rv, rv); 162 } 163 } 164 165 // APZ events are fired on the visual viewport and do not bubble up to the 166 // window. Manage that event listener separately here. 167 if (auto* win = nsGlobalWindowInner::Cast(mDocumentNode->GetInnerWindow())) { 168 win->VisualViewport()->AddEventListener(u"scroll"_ns, this, false, false); 169 } 170 171 return DocAccessible::AddEventListeners(); 172 } 173 174 nsresult RootAccessible::RemoveEventListeners() { 175 nsPIDOMWindowOuter* window = mDocumentNode->GetWindow(); 176 nsCOMPtr<EventTarget> target = window ? window->GetParentTarget() : nullptr; 177 if (target) { 178 for (const char *const *e = kEventTypes, *const *e_end = 179 std::end(kEventTypes); 180 e < e_end; ++e) { 181 target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true); 182 } 183 } 184 185 if (auto* win = nsGlobalWindowInner::Cast(mDocumentNode->GetInnerWindow())) { 186 win->VisualViewport()->RemoveEventListener(u"scroll"_ns, this, false); 187 } 188 189 // Do this before removing clearing caret accessible, so that it can use 190 // shutdown the caret accessible's selection listener 191 DocAccessible::RemoveEventListeners(); 192 return NS_OK; 193 } 194 195 //////////////////////////////////////////////////////////////////////////////// 196 // public 197 198 void RootAccessible::DocumentActivated(DocAccessible* aDocument) {} 199 200 //////////////////////////////////////////////////////////////////////////////// 201 // nsIDOMEventListener 202 203 NS_IMETHODIMP 204 RootAccessible::HandleEvent(Event* aDOMEvent) { 205 MOZ_ASSERT(aDOMEvent); 206 if (IsDefunct()) { 207 // Even though we've been shut down, RemoveEventListeners might not have 208 // removed the event handlers on the window's parent target if GetWindow 209 // returned null, so we might still get events here in this case. We should 210 // just ignore these events. 211 return NS_OK; 212 } 213 auto target = aDOMEvent->GetOriginalTarget(); 214 nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(target); 215 if (!origTargetNode) { 216 // The visual viewport isn't an nsINode, so if we're fielding 217 // a viewport event, we should end up here. 218 if (auto* win = 219 nsGlobalWindowInner::Cast(mDocumentNode->GetInnerWindow())) { 220 if (target == win->VisualViewport()) { 221 if (DocAccessible* d = PresShellPtr()->GetDocAccessible()) { 222 d->QueueCacheUpdate(d, CacheDomain::APZ); 223 } 224 } 225 } 226 return NS_OK; 227 } 228 229 #ifdef A11Y_LOG 230 if (logging::IsEnabled(logging::eDOMEvents)) { 231 nsAutoString eventType; 232 aDOMEvent->GetType(eventType); 233 logging::DOMEvent("handled", origTargetNode, eventType); 234 } 235 #endif 236 237 DocAccessible* document = 238 GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc()); 239 240 if (document) { 241 nsAutoString eventType; 242 aDOMEvent->GetType(eventType); 243 if (eventType.EqualsLiteral("scroll")) { 244 // We don't put this in the notification queue for 2 reasons: 245 // 1. We will flood the queue with repetitive events. 246 // 2. Since this doesn't necessarily touch layout, we are not 247 // guaranteed to have a WillRefresh tick any time soon. 248 document->HandleScroll(origTargetNode); 249 } else { 250 // Root accessible exists longer than any of its descendant documents so 251 // that we are guaranteed notification is processed before root accessible 252 // is destroyed. 253 // For shadow DOM, GetOriginalTarget on the Event returns null if we 254 // process the event async, so we must pass the target node as well. 255 document->HandleNotification<RootAccessible, Event, nsINode>( 256 this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode); 257 } 258 } 259 260 return NS_OK; 261 } 262 263 // RootAccessible protected 264 void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) { 265 MOZ_ASSERT(aDOMEvent); 266 MOZ_ASSERT(aTarget); 267 268 nsAutoString eventType; 269 aDOMEvent->GetType(eventType); 270 271 #ifdef A11Y_LOG 272 if (logging::IsEnabled(logging::eDOMEvents)) { 273 logging::DOMEvent("processed", aTarget, eventType); 274 } 275 #endif 276 277 if (eventType.EqualsLiteral("popuphiding")) { 278 HandlePopupHidingEvent(aTarget); 279 return; 280 } 281 282 DocAccessible* targetDocument = 283 GetAccService()->GetDocAccessible(aTarget->OwnerDoc()); 284 if (!targetDocument) { 285 // Document has ceased to exist. 286 return; 287 } 288 289 if (eventType.EqualsLiteral("popupshown") && 290 aTarget->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) { 291 targetDocument->ContentInserted(aTarget->AsContent(), 292 aTarget->GetNextSibling()); 293 return; 294 } 295 296 LocalAccessible* accessible = 297 targetDocument->GetAccessibleOrContainer(aTarget); 298 if (!accessible) return; 299 300 if (accessible->IsDoc() && eventType.EqualsLiteral("DOMTitleChanged")) { 301 targetDocument->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, 302 accessible); 303 return; 304 } 305 306 XULTreeAccessible* treeAcc = accessible->AsXULTree(); 307 if (treeAcc) { 308 if (eventType.EqualsLiteral("TreeRowCountChanged")) { 309 HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); 310 return; 311 } 312 313 if (eventType.EqualsLiteral("TreeInvalidated")) { 314 HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); 315 return; 316 } 317 } 318 319 if (eventType.EqualsLiteral("RadioStateChange")) { 320 uint64_t state = accessible->State(); 321 bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; 322 323 if (accessible->NeedsDOMUIEvent()) { 324 RefPtr<AccEvent> accEvent = 325 new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); 326 nsEventShell::FireEvent(accEvent); 327 } 328 329 if (isEnabled) { 330 FocusMgr()->ActiveItemChanged(accessible); 331 #ifdef A11Y_LOG 332 if (logging::IsEnabled(logging::eFocus)) { 333 logging::ActiveItemChangeCausedBy("RadioStateChange", accessible); 334 } 335 #endif 336 } 337 338 return; 339 } 340 341 if (eventType.EqualsLiteral("CheckboxStateChange")) { 342 if (accessible->NeedsDOMUIEvent()) { 343 uint64_t state = accessible->State(); 344 bool isEnabled = !!(state & states::CHECKED); 345 346 RefPtr<AccEvent> accEvent = 347 new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); 348 nsEventShell::FireEvent(accEvent); 349 } 350 return; 351 } 352 353 LocalAccessible* treeItemAcc = nullptr; 354 // If it's a tree element, need the currently selected item. 355 if (treeAcc) { 356 treeItemAcc = accessible->CurrentItem(); 357 if (treeItemAcc) accessible = treeItemAcc; 358 } 359 360 if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) { 361 uint64_t state = accessible->State(); 362 bool isEnabled = (state & states::EXPANDED) != 0; 363 364 RefPtr<AccEvent> accEvent = 365 new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); 366 nsEventShell::FireEvent(accEvent); 367 return; 368 } 369 370 nsINode* targetNode = accessible->GetNode(); 371 if (treeItemAcc && eventType.EqualsLiteral("select")) { 372 // XXX: We shouldn't be based on DOM select event which doesn't provide us 373 // any context info. We should integrate into nsTreeSelection instead. 374 // If multiselect tree, we should fire selectionadd or selection removed 375 if (FocusMgr()->HasDOMFocus(targetNode)) { 376 nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel = 377 targetNode->AsElement()->AsXULMultiSelectControl(); 378 if (!multiSel) { 379 // This shouldn't be possible. All XUL trees should have 380 // nsIDOMXULMultiSelectControlElement, and the tree is focused, so it 381 // shouldn't be dying. Nevertheless, this sometimes happens in the wild 382 // (bug 1597043). 383 MOZ_ASSERT_UNREACHABLE( 384 "XUL tree doesn't have nsIDOMXULMultiSelectControlElement"); 385 return; 386 } 387 nsAutoString selType; 388 multiSel->GetSelType(selType); 389 if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { 390 // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE 391 // for each tree item. Perhaps each tree item will need to cache its 392 // selection state and fire an event after a DOM "select" event when 393 // that state changes. XULTreeAccessible::UpdateTreeSelection(); 394 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, 395 accessible); 396 return; 397 } 398 399 RefPtr<AccSelChangeEvent> selChangeEvent = new AccSelChangeEvent( 400 treeAcc, treeItemAcc, AccSelChangeEvent::eSelectionAdd); 401 nsEventShell::FireEvent(selChangeEvent); 402 return; 403 } 404 } else if (eventType.EqualsLiteral("AlertActive")) { 405 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); 406 } else if (eventType.EqualsLiteral("popupshown")) { 407 HandlePopupShownEvent(accessible); 408 } else if (eventType.EqualsLiteral("DOMMenuInactive")) { 409 if (accessible->Role() == roles::MENUPOPUP) { 410 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, 411 accessible); 412 } 413 if (auto* focus = FocusMgr()->FocusedLocalAccessible()) { 414 // Intentionally use the content tree, because Linux strips menupopups 415 // from the a11y tree so accessible might be an arbitrary ancestor. 416 if (focus->GetContent() && 417 focus->GetContent()->IsShadowIncludingInclusiveDescendantOf( 418 aTarget)) { 419 // Move the focus to the topmost menu active content if any. The 420 // menu item in the parent menu will not fire a DOMMenuItemActive 421 // event if it's already active. 422 LocalAccessible* newActiveAccessible = nullptr; 423 if (auto* pm = nsXULPopupManager::GetInstance()) { 424 if (auto* content = pm->GetTopActiveMenuItemContent()) { 425 newActiveAccessible = 426 accessible->Document()->GetAccessible(content); 427 } 428 } 429 FocusMgr()->ActiveItemChanged(newActiveAccessible); 430 #ifdef A11Y_LOG 431 if (logging::IsEnabled(logging::eFocus)) { 432 logging::ActiveItemChangeCausedBy("DOMMenuInactive", 433 newActiveAccessible); 434 } 435 #endif 436 } 437 } 438 } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { 439 RefPtr<AccEvent> event = 440 new AccStateChangeEvent(accessible, states::ACTIVE, true); 441 nsEventShell::FireEvent(event); 442 FocusMgr()->ActiveItemChanged(accessible); 443 #ifdef A11Y_LOG 444 if (logging::IsEnabled(logging::eFocus)) { 445 logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); 446 } 447 #endif 448 } else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { 449 RefPtr<AccEvent> event = 450 new AccStateChangeEvent(accessible, states::ACTIVE, false); 451 nsEventShell::FireEvent(event); 452 453 // Process DOMMenuItemInactive event for autocomplete only because this is 454 // unique widget that may acquire focus from autocomplete popup while popup 455 // stays open and has no active item. In case of XUL tree autocomplete 456 // popup this event is fired for tree accessible. 457 LocalAccessible* widget = 458 accessible->IsWidget() ? accessible : accessible->ContainerWidget(); 459 if (widget && widget->IsAutoCompletePopup()) { 460 FocusMgr()->ActiveItemChanged(nullptr); 461 #ifdef A11Y_LOG 462 if (logging::IsEnabled(logging::eFocus)) { 463 logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible); 464 } 465 #endif 466 } 467 } else if (eventType.EqualsLiteral( 468 "DOMMenuBarActive")) { // Always from user input 469 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, accessible, 470 eFromUserInput); 471 472 // Notify of active item change when menubar gets active and if it has 473 // current item. This is a case of mouseover (set current menuitem) and 474 // mouse click (activate the menubar). If menubar doesn't have current item 475 // (can be a case of menubar activation from keyboard) then ignore this 476 // notification because later we'll receive DOMMenuItemActive event after 477 // current menuitem is set. 478 LocalAccessible* activeItem = accessible->CurrentItem(); 479 if (activeItem) { 480 FocusMgr()->ActiveItemChanged(activeItem); 481 #ifdef A11Y_LOG 482 if (logging::IsEnabled(logging::eFocus)) { 483 logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible); 484 } 485 #endif 486 } 487 } else if (eventType.EqualsLiteral( 488 "DOMMenuBarInactive")) { // Always from user input 489 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, accessible, 490 eFromUserInput); 491 492 FocusMgr()->ActiveItemChanged(nullptr); 493 #ifdef A11Y_LOG 494 if (logging::IsEnabled(logging::eFocus)) { 495 logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible); 496 } 497 #endif 498 } else if (accessible->NeedsDOMUIEvent() && 499 eventType.EqualsLiteral("ValueChange")) { 500 uint32_t event = accessible->HasNumericValue() 501 ? nsIAccessibleEvent::EVENT_VALUE_CHANGE 502 : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE; 503 targetDocument->FireDelayedEvent(event, accessible); 504 } 505 #ifdef DEBUG_DRAGDROPSTART 506 else if (eventType.EqualsLiteral("mouseover")) { 507 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, 508 accessible); 509 } 510 #endif 511 } 512 513 //////////////////////////////////////////////////////////////////////////////// 514 // LocalAccessible 515 516 void RootAccessible::Shutdown() { 517 // Called manually or by LocalAccessible::LastRelease() 518 if (HasShutdown()) { 519 return; 520 } 521 DocAccessibleWrap::Shutdown(); 522 } 523 524 Relation RootAccessible::RelationByType(RelationType aType) const { 525 if (!mDocumentNode || aType != RelationType::EMBEDS) { 526 return DocAccessibleWrap::RelationByType(aType); 527 } 528 529 if (RemoteAccessible* remoteDoc = GetPrimaryRemoteTopLevelContentDoc()) { 530 return Relation(remoteDoc); 531 } 532 533 if (nsIDocShell* docShell = mDocumentNode->GetDocShell()) { 534 nsCOMPtr<nsIDocShellTreeOwner> owner; 535 docShell->GetTreeOwner(getter_AddRefs(owner)); 536 if (owner) { 537 nsCOMPtr<nsIDocShellTreeItem> contentShell; 538 owner->GetPrimaryContentShell(getter_AddRefs(contentShell)); 539 if (contentShell) { 540 return Relation(nsAccUtils::GetDocAccessibleFor(contentShell)); 541 } 542 } 543 } 544 545 return Relation(); 546 } 547 548 //////////////////////////////////////////////////////////////////////////////// 549 // Protected members 550 551 void RootAccessible::HandlePopupShownEvent(LocalAccessible* aAccessible) { 552 roles::Role role = aAccessible->Role(); 553 554 if (role == roles::MENUPOPUP) { 555 // Don't fire menupopup events for combobox and autocomplete lists. 556 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, 557 aAccessible); 558 return; 559 } 560 561 if (role == roles::COMBOBOX_LIST) { 562 // Fire expanded state change event for comboboxes and autocompeletes. 563 LocalAccessible* combobox = aAccessible->LocalParent(); 564 if (!combobox) return; 565 566 if (combobox->IsCombobox()) { 567 RefPtr<AccEvent> event = 568 new AccStateChangeEvent(combobox, states::EXPANDED, true); 569 nsEventShell::FireEvent(event); 570 } 571 572 // If aria-activedescendant is present, redirect focus. 573 // This is needed for parent process <select> dropdowns, which use a 574 // menulist containing div elements instead of XUL menuitems. XUL menuitems 575 // fire DOMMenuItemActive events from layout instead. 576 MOZ_ASSERT(aAccessible->Elm()); 577 if (aAccessible->Elm()->HasAttr(nsGkAtoms::aria_activedescendant)) { 578 LocalAccessible* activeDescendant = aAccessible->CurrentItem(); 579 if (activeDescendant) { 580 FocusMgr()->ActiveItemChanged(activeDescendant, false); 581 #ifdef A11Y_LOG 582 if (logging::IsEnabled(logging::eFocus)) { 583 logging::ActiveItemChangeCausedBy("ARIA activedescendant on popup", 584 activeDescendant); 585 } 586 #endif 587 } 588 } 589 } 590 } 591 592 void RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) { 593 DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); 594 if (!document) { 595 return; 596 } 597 598 if (aPopupNode->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) { 599 document->ContentRemoved(aPopupNode->AsContent()); 600 return; 601 } 602 603 // Get popup accessible. There are cases when popup element isn't accessible 604 // but an underlying widget is and behaves like popup, an example is 605 // autocomplete popups. 606 LocalAccessible* popup = document->GetAccessible(aPopupNode); 607 if (!popup) { 608 LocalAccessible* popupContainer = 609 document->GetContainerAccessible(aPopupNode); 610 if (!popupContainer) { 611 return; 612 } 613 614 uint32_t childCount = popupContainer->ChildCount(); 615 for (uint32_t idx = 0; idx < childCount; idx++) { 616 LocalAccessible* child = popupContainer->LocalChildAt(idx); 617 if (child->IsAutoCompletePopup()) { 618 popup = child; 619 break; 620 } 621 } 622 623 // No popup no events. Focus is managed by DOM. This is a case for 624 // menupopups of menus on Linux since there are no accessible for popups. 625 if (!popup) { 626 return; 627 } 628 } 629 630 // In case of autocompletes and comboboxes fire state change event for 631 // expanded state. Note, HTML form autocomplete isn't a subject of state 632 // change event because they aren't autocompletes strictly speaking. 633 634 // HTML select is target of popuphidding event. Otherwise get container 635 // widget. No container widget means this is either tooltip or menupopup. 636 // No events in the former case. 637 LocalAccessible* widget = nullptr; 638 if (popup->IsCombobox()) { 639 widget = popup; 640 } else { 641 widget = popup->ContainerWidget(); 642 if (!widget) { 643 if (!popup->IsMenuPopup()) { 644 return; 645 } 646 widget = popup; 647 } 648 } 649 650 // Fire expanded state change event. 651 if (widget->IsCombobox()) { 652 RefPtr<AccEvent> event = 653 new AccStateChangeEvent(widget, states::EXPANDED, false); 654 document->FireDelayedEvent(event); 655 } 656 } 657 658 static void GetPropertyBagFromEvent(Event* aEvent, 659 nsIPropertyBag2** aPropertyBag) { 660 *aPropertyBag = nullptr; 661 662 CustomEvent* customEvent = aEvent->AsCustomEvent(); 663 if (!customEvent) return; 664 665 AutoJSAPI jsapi; 666 if (!jsapi.Init(customEvent->GetParentObject())) return; 667 668 JSContext* cx = jsapi.cx(); 669 JS::Rooted<JS::Value> detail(cx); 670 customEvent->GetDetail(cx, &detail); 671 if (!detail.isObject()) return; 672 673 JS::Rooted<JSObject*> detailObj(cx, &detail.toObject()); 674 675 nsresult rv; 676 nsCOMPtr<nsIPropertyBag2> propBag; 677 rv = UnwrapArg<nsIPropertyBag2>(cx, detailObj, getter_AddRefs(propBag)); 678 if (NS_FAILED(rv)) return; 679 680 propBag.forget(aPropertyBag); 681 } 682 683 void RootAccessible::HandleTreeRowCountChangedEvent( 684 Event* aEvent, XULTreeAccessible* aAccessible) { 685 nsCOMPtr<nsIPropertyBag2> propBag; 686 GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); 687 if (!propBag) return; 688 689 nsresult rv; 690 int32_t index, count; 691 rv = propBag->GetPropertyAsInt32(u"index"_ns, &index); 692 if (NS_FAILED(rv)) return; 693 694 rv = propBag->GetPropertyAsInt32(u"count"_ns, &count); 695 if (NS_FAILED(rv)) return; 696 697 aAccessible->InvalidateCache(index, count); 698 } 699 700 void RootAccessible::HandleTreeInvalidatedEvent( 701 Event* aEvent, XULTreeAccessible* aAccessible) { 702 nsCOMPtr<nsIPropertyBag2> propBag; 703 GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); 704 if (!propBag) return; 705 706 int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1; 707 propBag->GetPropertyAsInt32(u"startrow"_ns, &startRow); 708 propBag->GetPropertyAsInt32(u"endrow"_ns, &endRow); 709 propBag->GetPropertyAsInt32(u"startcolumn"_ns, &startCol); 710 propBag->GetPropertyAsInt32(u"endcolumn"_ns, &endCol); 711 712 aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol); 713 } 714 715 RemoteAccessible* RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const { 716 nsCOMPtr<nsIDocShellTreeOwner> owner; 717 mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner)); 718 NS_ENSURE_TRUE(owner, nullptr); 719 720 nsCOMPtr<nsIRemoteTab> remoteTab; 721 owner->GetPrimaryRemoteTab(getter_AddRefs(remoteTab)); 722 if (!remoteTab) { 723 return nullptr; 724 } 725 726 auto tab = static_cast<dom::BrowserHost*>(remoteTab.get()); 727 DocAccessibleParent* doc = tab->GetTopLevelDocAccessible(); 728 // If doc has no parent, it isn't currently attached to the tree and isn't 729 // interactive. This happens when the Terms of Use modal is displayed, which 730 // blocks all other interaction with the browser. In this case, we should 731 // behave as if there is no primary remote top level content document. 732 return doc && doc->Parent() ? doc : nullptr; 733 }