uiaRawElmProvider.cpp (48076B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "uiaRawElmProvider.h" 8 9 #include <comdef.h> 10 #include <uiautomationcoreapi.h> 11 12 #include "AccAttributes.h" 13 #include "AccessibleWrap.h" 14 #include "ApplicationAccessible.h" 15 #include "ARIAMap.h" 16 #include "ia2AccessibleHypertext.h" 17 #include "ia2AccessibleTable.h" 18 #include "ia2AccessibleTableCell.h" 19 #include "LocalAccessible-inl.h" 20 #include "mozilla/a11y/Compatibility.h" 21 #include "mozilla/a11y/RemoteAccessible.h" 22 #include "MsaaAccessible.h" 23 #include "MsaaRootAccessible.h" 24 #include "nsAccessibilityService.h" 25 #include "nsAccUtils.h" 26 #include "nsIAccessiblePivot.h" 27 #include "nsTextEquivUtils.h" 28 #include "Pivot.h" 29 #include "Relation.h" 30 #include "RootAccessible.h" 31 #include "TextLeafRange.h" 32 #include "UiaText.h" 33 #include "UiaTextRange.h" 34 35 using namespace mozilla; 36 using namespace mozilla::a11y; 37 38 // Helper functions 39 40 static ToggleState ToToggleState(uint64_t aState) { 41 if (aState & states::MIXED) { 42 return ToggleState_Indeterminate; 43 } 44 if (aState & (states::CHECKED | states::PRESSED)) { 45 return ToggleState_On; 46 } 47 return ToggleState_Off; 48 } 49 50 static ExpandCollapseState ToExpandCollapseState(uint64_t aState) { 51 if (aState & states::EXPANDED) { 52 return ExpandCollapseState_Expanded; 53 } 54 // If aria-haspopup is specified without aria-expanded, we should still expose 55 // collapsed, since aria-haspopup infers that it can be expanded. The 56 // alternative is ExpandCollapseState_LeafNode, but that means that the 57 // element can't be expanded nor collapsed. 58 if (aState & (states::COLLAPSED | states::HASPOPUP)) { 59 return ExpandCollapseState_Collapsed; 60 } 61 return ExpandCollapseState_LeafNode; 62 } 63 64 static bool IsRadio(Accessible* aAcc) { 65 role r = aAcc->Role(); 66 return r == roles::RADIOBUTTON || r == roles::RADIO_MENU_ITEM; 67 } 68 69 // Used to search for a text leaf descendant for the LabeledBy property. 70 class LabelTextLeafRule : public PivotRule { 71 public: 72 virtual uint16_t Match(Accessible* aAcc) override { 73 if (aAcc->IsTextLeaf()) { 74 nsAutoString name; 75 aAcc->Name(name); 76 if (name.IsEmpty() || name.EqualsLiteral(" ")) { 77 // An empty or white space text leaf isn't useful as a label. 78 return nsIAccessibleTraversalRule::FILTER_IGNORE; 79 } 80 return nsIAccessibleTraversalRule::FILTER_MATCH; 81 } 82 if (!nsTextEquivUtils::HasNameRule(aAcc, eNameFromSubtreeIfReqRule)) { 83 // Don't descend into things that can't be used as label content; e.g. 84 // text boxes. 85 return nsIAccessibleTraversalRule::FILTER_IGNORE | 86 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 87 } 88 return nsIAccessibleTraversalRule::FILTER_IGNORE; 89 } 90 }; 91 92 static void MaybeRaiseUiaLiveRegionEvent(Accessible* aAcc, 93 uint32_t aGeckoEvent) { 94 if (Accessible* live = nsAccUtils::GetLiveRegionRoot(aAcc)) { 95 auto* uia = MsaaAccessible::GetFrom(live); 96 ::UiaRaiseAutomationEvent(uia, UIA_LiveRegionChangedEventId); 97 } 98 } 99 100 static bool HasTextPattern(Accessible* aAcc) { 101 // The Text pattern must be supported for documents and editable text controls 102 // on the web: 103 // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview#webpage-and-text-input-controls-in-edge 104 // It is also recommended that the Text pattern be supported for the Text 105 // control type: 106 // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-about-text-and-textrange-patterns#control-types 107 // If we don't support this for the Text control type, when Narrator is 108 // continuously reading a document, it doesn't respect the starting position 109 // of the cursor and doesn't move the cursor as it reads. See bug 1949920. 110 constexpr uint64_t editableRootStates = states::EDITABLE | states::FOCUSABLE; 111 return aAcc->IsText() || (aAcc->IsDoc() && !aAcc->IsRoot()) || 112 (aAcc->IsHyperText() && 113 (aAcc->State() & editableRootStates) == editableRootStates); 114 } 115 116 static Accessible* GetTextContainer(Accessible* aDescendant) { 117 // "An element that implements the TextChild control pattern must be a child, 118 // or descendent, of an element that supports the Text control pattern." 119 // https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itextchildprovider#remarks 120 for (Accessible* ancestor = aDescendant->Parent(); ancestor; 121 ancestor = ancestor->Parent()) { 122 if (HasTextPattern(ancestor)) { 123 return ancestor; 124 } 125 } 126 return nullptr; 127 } 128 129 static MsaaAccessible* GetTextPatternProviderFor(Accessible* aOrigin) { 130 if (HasTextPattern(aOrigin)) { 131 return MsaaAccessible::GetFrom(aOrigin); 132 } 133 return MsaaAccessible::GetFrom(GetTextContainer(aOrigin)); 134 } 135 136 static bool MustSelectUsingDoAction(Accessible* aAcc) { 137 return IsRadio(aAcc) || aAcc->Role() == roles::PAGETAB; 138 } 139 140 //////////////////////////////////////////////////////////////////////////////// 141 // uiaRawElmProvider 142 //////////////////////////////////////////////////////////////////////////////// 143 144 Accessible* uiaRawElmProvider::Acc() const { 145 return static_cast<const MsaaAccessible*>(this)->Acc(); 146 } 147 148 /* static */ 149 void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc, 150 uint32_t aGeckoEvent) { 151 if (!Compatibility::IsUiaEnabled() || !::UiaClientsAreListening()) { 152 return; 153 } 154 auto* uia = MsaaAccessible::GetFrom(aAcc); 155 if (!uia) { 156 return; 157 } 158 // Some UIA events include or depend on data that might not be cached yet. We 159 // shouldn't request additional cache domains in this case because a client 160 // might not even care about these events. Instead, we use explicit client 161 // queries as a signal to request domains. 162 CacheDomainActivationBlocker cacheBlocker; 163 PROPERTYID property = 0; 164 _variant_t newVal; 165 bool gotNewVal = false; 166 // For control pattern properties, we can't use GetPropertyValue. In those 167 // cases, we must set newVal appropriately and set gotNewVal to true. 168 switch (aGeckoEvent) { 169 case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE: 170 property = UIA_FullDescriptionPropertyId; 171 break; 172 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: 173 // There is a UiaRaiseAsyncContentLoadedEvent function, but the client API 174 // doesn't have a specialized event handler for this event. Also, Chromium 175 // uses UiaRaiseAutomationEvent for this event. 176 ::UiaRaiseAutomationEvent(uia, UIA_AsyncContentLoadedEventId); 177 return; 178 case nsIAccessibleEvent::EVENT_FOCUS: 179 ::UiaRaiseAutomationEvent(uia, UIA_AutomationFocusChangedEventId); 180 return; 181 case nsIAccessibleEvent::EVENT_NAME_CHANGE: 182 property = UIA_NamePropertyId; 183 MaybeRaiseUiaLiveRegionEvent(aAcc, aGeckoEvent); 184 break; 185 case nsIAccessibleEvent::EVENT_SELECTION: 186 ::UiaRaiseAutomationEvent(uia, UIA_SelectionItem_ElementSelectedEventId); 187 return; 188 case nsIAccessibleEvent::EVENT_SELECTION_ADD: 189 ::UiaRaiseAutomationEvent( 190 uia, UIA_SelectionItem_ElementAddedToSelectionEventId); 191 return; 192 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: 193 ::UiaRaiseAutomationEvent( 194 uia, UIA_SelectionItem_ElementRemovedFromSelectionEventId); 195 return; 196 case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: 197 ::UiaRaiseAutomationEvent(uia, UIA_Selection_InvalidatedEventId); 198 return; 199 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: 200 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: 201 ::UiaRaiseAutomationEvent(GetTextPatternProviderFor(aAcc), 202 UIA_Text_TextSelectionChangedEventId); 203 return; 204 case nsIAccessibleEvent::EVENT_TEXT_INSERTED: 205 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: 206 ::UiaRaiseAutomationEvent(GetTextPatternProviderFor(aAcc), 207 UIA_Text_TextChangedEventId); 208 MaybeRaiseUiaLiveRegionEvent(aAcc, aGeckoEvent); 209 return; 210 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: 211 property = UIA_ValueValuePropertyId; 212 newVal.vt = VT_BSTR; 213 uia->get_Value(&newVal.bstrVal); 214 gotNewVal = true; 215 break; 216 case nsIAccessibleEvent::EVENT_VALUE_CHANGE: 217 property = UIA_RangeValueValuePropertyId; 218 newVal.vt = VT_R8; 219 uia->get_Value(&newVal.dblVal); 220 gotNewVal = true; 221 break; 222 } 223 if (property) { 224 // We can't get the old value. Thankfully, clients don't seem to need it. 225 _variant_t oldVal; 226 if (!gotNewVal) { 227 // This isn't a pattern property, so we can use GetPropertyValue. 228 uia->GetPropertyValue(property, &newVal); 229 } 230 ::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal); 231 } 232 } 233 234 /* static */ 235 void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc, 236 uint64_t aState, 237 bool aEnabled) { 238 if (!Compatibility::IsUiaEnabled() || !::UiaClientsAreListening()) { 239 return; 240 } 241 auto* uia = MsaaAccessible::GetFrom(aAcc); 242 if (!uia) { 243 return; 244 } 245 PROPERTYID property = 0; 246 _variant_t newVal; 247 switch (aState) { 248 case states::CHECKED: 249 if (aEnabled && IsRadio(aAcc)) { 250 ::UiaRaiseAutomationEvent(uia, 251 UIA_SelectionItem_ElementSelectedEventId); 252 return; 253 } 254 // For other checkable things, the Toggle pattern is used. 255 [[fallthrough]]; 256 case states::MIXED: 257 case states::PRESSED: 258 property = UIA_ToggleToggleStatePropertyId; 259 newVal.vt = VT_I4; 260 newVal.lVal = ToToggleState(aEnabled ? aState : 0); 261 break; 262 case states::COLLAPSED: 263 case states::EXPANDED: 264 case states::HASPOPUP: 265 property = UIA_ExpandCollapseExpandCollapseStatePropertyId; 266 newVal.vt = VT_I4; 267 newVal.lVal = ToExpandCollapseState(aEnabled ? aState : 0); 268 break; 269 case states::UNAVAILABLE: 270 property = UIA_IsEnabledPropertyId; 271 newVal.vt = VT_BOOL; 272 newVal.boolVal = aEnabled ? VARIANT_FALSE : VARIANT_TRUE; 273 break; 274 default: 275 return; 276 } 277 MOZ_ASSERT(property); 278 // We can't get the old value. Thankfully, clients don't seem to need it. 279 _variant_t oldVal; 280 ::UiaRaiseAutomationPropertyChangedEvent(uia, property, oldVal, newVal); 281 } 282 283 // IUnknown 284 285 STDMETHODIMP 286 uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) { 287 *aInterface = nullptr; 288 if (aIid == IID_IAccessibleEx) { 289 *aInterface = static_cast<IAccessibleEx*>(this); 290 } else if (aIid == IID_IRawElementProviderSimple) { 291 *aInterface = static_cast<IRawElementProviderSimple*>(this); 292 } else if (aIid == IID_IRawElementProviderFragment) { 293 *aInterface = static_cast<IRawElementProviderFragment*>(this); 294 } else if (aIid == IID_IExpandCollapseProvider) { 295 *aInterface = static_cast<IExpandCollapseProvider*>(this); 296 } else if (aIid == IID_IInvokeProvider) { 297 *aInterface = static_cast<IInvokeProvider*>(this); 298 } else if (aIid == IID_IRangeValueProvider) { 299 *aInterface = static_cast<IRangeValueProvider*>(this); 300 } else if (aIid == IID_IScrollItemProvider) { 301 *aInterface = static_cast<IScrollItemProvider*>(this); 302 } else if (aIid == IID_ISelectionItemProvider) { 303 *aInterface = static_cast<ISelectionItemProvider*>(this); 304 } else if (aIid == IID_ISelectionProvider) { 305 *aInterface = static_cast<ISelectionProvider*>(this); 306 } else if (aIid == IID_ITextChildProvider) { 307 *aInterface = static_cast<ITextChildProvider*>(this); 308 } else if (aIid == IID_IToggleProvider) { 309 *aInterface = static_cast<IToggleProvider*>(this); 310 } else if (aIid == IID_IValueProvider) { 311 *aInterface = static_cast<IValueProvider*>(this); 312 } else { 313 return E_NOINTERFACE; 314 } 315 MOZ_ASSERT(*aInterface); 316 static_cast<MsaaAccessible*>(this)->AddRef(); 317 return S_OK; 318 } 319 320 //////////////////////////////////////////////////////////////////////////////// 321 // IAccessibleEx 322 323 STDMETHODIMP 324 uiaRawElmProvider::GetObjectForChild( 325 long aIdChild, __RPC__deref_out_opt IAccessibleEx** aAccEx) { 326 if (!aAccEx) return E_INVALIDARG; 327 328 *aAccEx = nullptr; 329 330 return Acc() ? S_OK : CO_E_OBJNOTCONNECTED; 331 } 332 333 STDMETHODIMP 334 uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc, 335 __RPC__out long* aIdChild) { 336 if (!aAcc || !aIdChild) return E_INVALIDARG; 337 338 *aAcc = nullptr; 339 *aIdChild = 0; 340 341 if (!Acc()) { 342 return CO_E_OBJNOTCONNECTED; 343 } 344 345 *aIdChild = CHILDID_SELF; 346 RefPtr<IAccessible> copy = static_cast<MsaaAccessible*>(this); 347 copy.forget(aAcc); 348 349 return S_OK; 350 } 351 352 STDMETHODIMP 353 uiaRawElmProvider::GetRuntimeId(__RPC__deref_out_opt SAFEARRAY** aRuntimeIds) { 354 if (!aRuntimeIds) return E_INVALIDARG; 355 Accessible* acc = Acc(); 356 if (!acc) { 357 return CO_E_OBJNOTCONNECTED; 358 } 359 360 int ids[] = {UiaAppendRuntimeId, MsaaAccessible::GetChildIDFor(acc)}; 361 *aRuntimeIds = SafeArrayCreateVector(VT_I4, 0, 2); 362 if (!*aRuntimeIds) return E_OUTOFMEMORY; 363 364 for (LONG i = 0; i < (LONG)std::size(ids); i++) 365 SafeArrayPutElement(*aRuntimeIds, &i, (void*)&(ids[i])); 366 367 return S_OK; 368 } 369 370 STDMETHODIMP 371 uiaRawElmProvider::ConvertReturnedElement( 372 __RPC__in_opt IRawElementProviderSimple* aRawElmProvider, 373 __RPC__deref_out_opt IAccessibleEx** aAccEx) { 374 if (!aRawElmProvider || !aAccEx) return E_INVALIDARG; 375 376 *aAccEx = nullptr; 377 378 void* instancePtr = nullptr; 379 HRESULT hr = aRawElmProvider->QueryInterface(IID_IAccessibleEx, &instancePtr); 380 if (SUCCEEDED(hr)) *aAccEx = static_cast<IAccessibleEx*>(instancePtr); 381 382 return hr; 383 } 384 385 //////////////////////////////////////////////////////////////////////////////// 386 // IRawElementProviderSimple 387 388 STDMETHODIMP 389 uiaRawElmProvider::get_ProviderOptions( 390 __RPC__out enum ProviderOptions* aOptions) { 391 if (!aOptions) return E_INVALIDARG; 392 393 *aOptions = kProviderOptions; 394 return S_OK; 395 } 396 397 STDMETHODIMP 398 uiaRawElmProvider::GetPatternProvider( 399 PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) { 400 if (!aPatternProvider) return E_INVALIDARG; 401 *aPatternProvider = nullptr; 402 Accessible* acc = Acc(); 403 if (!acc) { 404 return CO_E_OBJNOTCONNECTED; 405 } 406 switch (aPatternId) { 407 case UIA_ExpandCollapsePatternId: 408 if (HasExpandCollapsePattern()) { 409 RefPtr<IExpandCollapseProvider> expand = this; 410 expand.forget(aPatternProvider); 411 } 412 return S_OK; 413 case UIA_GridPatternId: 414 if (acc->IsTable()) { 415 auto grid = GetPatternFromDerived<ia2AccessibleTable, IGridProvider>(); 416 grid.forget(aPatternProvider); 417 } 418 return S_OK; 419 case UIA_GridItemPatternId: 420 if (acc->IsTableCell()) { 421 auto item = 422 GetPatternFromDerived<ia2AccessibleTableCell, IGridItemProvider>(); 423 item.forget(aPatternProvider); 424 } 425 return S_OK; 426 case UIA_InvokePatternId: 427 // Per the UIA documentation, we should only expose the Invoke pattern "if 428 // the same behavior is not exposed through another control pattern 429 // provider". 430 if (acc->ActionCount() > 0 && !HasTogglePattern() && 431 !HasExpandCollapsePattern() && !HasSelectionItemPattern()) { 432 RefPtr<IInvokeProvider> invoke = this; 433 invoke.forget(aPatternProvider); 434 } 435 return S_OK; 436 case UIA_RangeValuePatternId: 437 if (acc->HasNumericValue()) { 438 RefPtr<IValueProvider> value = this; 439 value.forget(aPatternProvider); 440 } 441 return S_OK; 442 case UIA_ScrollItemPatternId: { 443 RefPtr<IScrollItemProvider> scroll = this; 444 scroll.forget(aPatternProvider); 445 return S_OK; 446 } 447 case UIA_SelectionItemPatternId: 448 if (HasSelectionItemPattern()) { 449 RefPtr<ISelectionItemProvider> item = this; 450 item.forget(aPatternProvider); 451 } 452 return S_OK; 453 case UIA_SelectionPatternId: 454 // According to the UIA documentation, radio button groups should support 455 // the Selection pattern. However: 456 // 1. The Core AAM spec doesn't specify the Selection pattern for 457 // the radiogroup role. 458 // 2. HTML radio buttons might not be contained by a dedicated group. 459 // 3. Chromium exposes the Selection pattern on radio groups, but it 460 // doesn't expose any selected items, even when there is a checked radio 461 // child. 462 // 4. Radio menu items are similar to radio buttons and all the above 463 // also applies to menus. 464 // For now, we don't support the Selection pattern for radio groups or 465 // menus, only for list boxes, tab lists, etc. 466 if (acc->IsSelect()) { 467 RefPtr<ISelectionProvider> selection = this; 468 selection.forget(aPatternProvider); 469 } 470 return S_OK; 471 case UIA_TablePatternId: 472 if (acc->IsTable()) { 473 auto table = 474 GetPatternFromDerived<ia2AccessibleTable, ITableProvider>(); 475 table.forget(aPatternProvider); 476 } 477 return S_OK; 478 case UIA_TableItemPatternId: 479 if (acc->IsTableCell()) { 480 auto item = 481 GetPatternFromDerived<ia2AccessibleTableCell, ITableItemProvider>(); 482 item.forget(aPatternProvider); 483 } 484 return S_OK; 485 case UIA_TextChildPatternId: 486 if (GetTextContainer(acc)) { 487 RefPtr<ITextChildProvider> textChild = this; 488 textChild.forget(aPatternProvider); 489 } 490 return S_OK; 491 case UIA_TextPatternId: 492 if (HasTextPattern(acc)) { 493 RefPtr<ITextProvider> text = 494 new UiaText(static_cast<MsaaAccessible*>(this)); 495 text.forget(aPatternProvider); 496 } 497 return S_OK; 498 case UIA_TogglePatternId: 499 if (HasTogglePattern()) { 500 RefPtr<IToggleProvider> toggle = this; 501 toggle.forget(aPatternProvider); 502 } 503 return S_OK; 504 case UIA_ValuePatternId: 505 if (HasValuePattern()) { 506 RefPtr<IValueProvider> value = this; 507 value.forget(aPatternProvider); 508 } 509 return S_OK; 510 } 511 return S_OK; 512 } 513 514 STDMETHODIMP 515 uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, 516 __RPC__out VARIANT* aPropertyValue) { 517 if (!aPropertyValue) return E_INVALIDARG; 518 519 Accessible* acc = Acc(); 520 if (!acc) { 521 return CO_E_OBJNOTCONNECTED; 522 } 523 LocalAccessible* localAcc = acc->AsLocal(); 524 525 aPropertyValue->vt = VT_EMPTY; 526 527 switch (aPropertyId) { 528 // Accelerator Key / shortcut. 529 case UIA_AcceleratorKeyPropertyId: { 530 nsAutoString keyString; 531 532 if (!acc->GetStringARIAAttr(nsGkAtoms::aria_keyshortcuts, keyString)) { 533 if (localAcc) { 534 // KeyboardShortcut is only currently relevant for LocalAccessible. 535 localAcc->KeyboardShortcut().ToString(keyString); 536 } 537 } 538 539 if (!keyString.IsEmpty()) { 540 aPropertyValue->vt = VT_BSTR; 541 aPropertyValue->bstrVal = ::SysAllocString(keyString.get()); 542 return S_OK; 543 } 544 545 break; 546 } 547 548 // Access Key / mneumonic. 549 case UIA_AccessKeyPropertyId: { 550 nsAutoString keyString; 551 552 acc->AccessKey().ToString(keyString); 553 554 if (!keyString.IsEmpty()) { 555 aPropertyValue->vt = VT_BSTR; 556 aPropertyValue->bstrVal = ::SysAllocString(keyString.get()); 557 return S_OK; 558 } 559 560 break; 561 } 562 563 case UIA_AriaRolePropertyId: { 564 nsAutoString role; 565 if (acc->HasARIARole()) { 566 RefPtr<AccAttributes> attributes = acc->Attributes(); 567 attributes->GetAttribute(nsGkAtoms::xmlroles, role); 568 } else if (nsStaticAtom* computed = acc->ComputedARIARole()) { 569 computed->ToString(role); 570 } 571 if (!role.IsEmpty()) { 572 aPropertyValue->vt = VT_BSTR; 573 aPropertyValue->bstrVal = ::SysAllocString(role.get()); 574 return S_OK; 575 } 576 break; 577 } 578 579 // ARIA Properties 580 case UIA_AriaPropertiesPropertyId: { 581 nsAutoString ariaProperties; 582 // We only expose the properties we need to expose here. 583 nsAutoString live; 584 nsAccUtils::GetLiveRegionSetting(acc, live); 585 if (!live.IsEmpty()) { 586 // This is a live region root. The live setting is already exposed via 587 // the LiveSetting property. However, there is no UIA property for 588 // atomic. 589 Maybe<bool> atomic; 590 acc->LiveRegionAttributes(nullptr, nullptr, &atomic, nullptr); 591 if (atomic && *atomic) { 592 ariaProperties.AppendLiteral("atomic=true"); 593 } else { 594 // Narrator assumes a default of true, so we need to output the 595 // correct default (false) even if the attribute isn't specified. 596 ariaProperties.AppendLiteral("atomic=false"); 597 } 598 } 599 if (acc->HasCustomActions()) { 600 if (!ariaProperties.IsEmpty()) { 601 ariaProperties += ';'; 602 } 603 ariaProperties.AppendLiteral("hasactions=true"); 604 } 605 if (!ariaProperties.IsEmpty()) { 606 aPropertyValue->vt = VT_BSTR; 607 aPropertyValue->bstrVal = ::SysAllocString(ariaProperties.get()); 608 return S_OK; 609 } 610 break; 611 } 612 613 case UIA_AutomationIdPropertyId: { 614 nsAutoString id; 615 acc->DOMNodeID(id); 616 if (!id.IsEmpty()) { 617 aPropertyValue->vt = VT_BSTR; 618 aPropertyValue->bstrVal = ::SysAllocString(id.get()); 619 return S_OK; 620 } 621 break; 622 } 623 624 case UIA_ClassNamePropertyId: { 625 nsAutoString className; 626 acc->DOMNodeClass(className); 627 if (!className.IsEmpty()) { 628 aPropertyValue->vt = VT_BSTR; 629 aPropertyValue->bstrVal = ::SysAllocString(className.get()); 630 return S_OK; 631 } 632 break; 633 } 634 635 case UIA_ControllerForPropertyId: 636 aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; 637 aPropertyValue->parray = AccRelationsToUiaArray( 638 {RelationType::CONTROLLER_FOR, RelationType::ERRORMSG}); 639 return S_OK; 640 641 case UIA_ControlTypePropertyId: 642 aPropertyValue->vt = VT_I4; 643 aPropertyValue->lVal = GetControlType(); 644 break; 645 646 case UIA_DescribedByPropertyId: 647 aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; 648 aPropertyValue->parray = AccRelationsToUiaArray( 649 {RelationType::DESCRIBED_BY, RelationType::DETAILS}); 650 return S_OK; 651 652 case UIA_FlowsFromPropertyId: 653 aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; 654 aPropertyValue->parray = 655 AccRelationsToUiaArray({RelationType::FLOWS_FROM}); 656 return S_OK; 657 658 case UIA_FlowsToPropertyId: 659 aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; 660 aPropertyValue->parray = AccRelationsToUiaArray({RelationType::FLOWS_TO}); 661 return S_OK; 662 663 case UIA_FrameworkIdPropertyId: 664 if (ApplicationAccessible* app = ApplicationAcc()) { 665 nsAutoString name; 666 app->PlatformName(name); 667 if (!name.IsEmpty()) { 668 aPropertyValue->vt = VT_BSTR; 669 aPropertyValue->bstrVal = ::SysAllocString(name.get()); 670 return S_OK; 671 } 672 } 673 break; 674 675 case UIA_FullDescriptionPropertyId: { 676 nsAutoString desc; 677 acc->Description(desc); 678 if (!desc.IsEmpty()) { 679 aPropertyValue->vt = VT_BSTR; 680 aPropertyValue->bstrVal = ::SysAllocString(desc.get()); 681 return S_OK; 682 } 683 break; 684 } 685 686 case UIA_HasKeyboardFocusPropertyId: 687 aPropertyValue->vt = VT_BOOL; 688 aPropertyValue->boolVal = VARIANT_FALSE; 689 if (auto* focusMgr = FocusMgr()) { 690 if (focusMgr->IsFocused(acc)) { 691 aPropertyValue->boolVal = VARIANT_TRUE; 692 } 693 } 694 return S_OK; 695 696 case UIA_IsContentElementPropertyId: 697 case UIA_IsControlElementPropertyId: 698 aPropertyValue->vt = VT_BOOL; 699 aPropertyValue->boolVal = IsControl() ? VARIANT_TRUE : VARIANT_FALSE; 700 return S_OK; 701 702 case UIA_IsEnabledPropertyId: 703 aPropertyValue->vt = VT_BOOL; 704 aPropertyValue->boolVal = 705 (acc->State() & states::UNAVAILABLE) ? VARIANT_FALSE : VARIANT_TRUE; 706 return S_OK; 707 708 case UIA_IsKeyboardFocusablePropertyId: 709 aPropertyValue->vt = VT_BOOL; 710 aPropertyValue->boolVal = 711 (acc->State() & states::FOCUSABLE) ? VARIANT_TRUE : VARIANT_FALSE; 712 return S_OK; 713 714 case UIA_IsOffscreenPropertyId: 715 aPropertyValue->vt = VT_BOOL; 716 aPropertyValue->boolVal = 717 (acc->State() & states::OFFSCREEN) ? VARIANT_TRUE : VARIANT_FALSE; 718 return S_OK; 719 720 case UIA_IsPasswordPropertyId: 721 aPropertyValue->vt = VT_BOOL; 722 aPropertyValue->boolVal = 723 (acc->State() & states::PROTECTED) ? VARIANT_TRUE : VARIANT_FALSE; 724 return S_OK; 725 726 case UIA_LabeledByPropertyId: 727 if (Accessible* target = GetLabeledBy()) { 728 aPropertyValue->vt = VT_UNKNOWN; 729 RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(target); 730 uia.forget(&aPropertyValue->punkVal); 731 return S_OK; 732 } 733 break; 734 735 case UIA_LandmarkTypePropertyId: 736 if (long type = GetLandmarkType()) { 737 aPropertyValue->vt = VT_I4; 738 aPropertyValue->lVal = type; 739 return S_OK; 740 } 741 break; 742 743 case UIA_LevelPropertyId: 744 aPropertyValue->vt = VT_I4; 745 aPropertyValue->lVal = acc->GroupPosition().level; 746 return S_OK; 747 748 case UIA_LiveSettingPropertyId: 749 aPropertyValue->vt = VT_I4; 750 aPropertyValue->lVal = GetLiveSetting(); 751 return S_OK; 752 753 case UIA_LocalizedLandmarkTypePropertyId: { 754 nsAutoString landmark; 755 GetLocalizedLandmarkType(landmark); 756 if (!landmark.IsEmpty()) { 757 aPropertyValue->vt = VT_BSTR; 758 aPropertyValue->bstrVal = ::SysAllocString(landmark.get()); 759 return S_OK; 760 } 761 break; 762 } 763 764 case UIA_NamePropertyId: { 765 nsAutoString name; 766 acc->Name(name); 767 if (!name.IsEmpty()) { 768 aPropertyValue->vt = VT_BSTR; 769 aPropertyValue->bstrVal = ::SysAllocString(name.get()); 770 return S_OK; 771 } 772 break; 773 } 774 775 case UIA_PositionInSetPropertyId: 776 aPropertyValue->vt = VT_I4; 777 aPropertyValue->lVal = acc->GroupPosition().posInSet; 778 return S_OK; 779 780 case UIA_SizeOfSetPropertyId: 781 aPropertyValue->vt = VT_I4; 782 aPropertyValue->lVal = acc->GroupPosition().setSize; 783 return S_OK; 784 785 default: { 786 // These can't be included as case statements because they are not 787 // constant expressions. 788 const UiaRegistrations& registrations = GetUiaRegistrations(); 789 if (aPropertyId == registrations.mAccessibleActions) { 790 aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; 791 aPropertyValue->parray = AccRelationsToUiaArray({RelationType::ACTION}); 792 return S_OK; 793 } 794 } 795 } 796 797 return S_OK; 798 } 799 800 STDMETHODIMP 801 uiaRawElmProvider::get_HostRawElementProvider( 802 __RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider) { 803 if (!aRawElmProvider) return E_INVALIDARG; 804 *aRawElmProvider = nullptr; 805 Accessible* acc = Acc(); 806 if (!acc) { 807 return CO_E_OBJNOTCONNECTED; 808 } 809 if (acc->IsRoot()) { 810 HWND hwnd = MsaaAccessible::GetHWNDFor(acc); 811 return UiaHostProviderFromHwnd(hwnd, aRawElmProvider); 812 } 813 return S_OK; 814 } 815 816 // IRawElementProviderFragment 817 818 STDMETHODIMP 819 uiaRawElmProvider::Navigate( 820 enum NavigateDirection aDirection, 821 __RPC__deref_out_opt IRawElementProviderFragment** aRetVal) { 822 if (!aRetVal) { 823 return E_INVALIDARG; 824 } 825 *aRetVal = nullptr; 826 Accessible* acc = Acc(); 827 if (!acc) { 828 return CO_E_OBJNOTCONNECTED; 829 } 830 Accessible* target = nullptr; 831 switch (aDirection) { 832 case NavigateDirection_Parent: 833 if (!acc->IsRoot()) { 834 target = acc->Parent(); 835 } 836 break; 837 case NavigateDirection_NextSibling: 838 if (!acc->IsRoot()) { 839 target = acc->NextSibling(); 840 } 841 break; 842 case NavigateDirection_PreviousSibling: 843 if (!acc->IsRoot()) { 844 target = acc->PrevSibling(); 845 } 846 break; 847 case NavigateDirection_FirstChild: 848 if (!nsAccUtils::MustPrune(acc)) { 849 target = acc->FirstChild(); 850 } 851 break; 852 case NavigateDirection_LastChild: 853 if (!nsAccUtils::MustPrune(acc)) { 854 target = acc->LastChild(); 855 } 856 break; 857 default: 858 return E_INVALIDARG; 859 } 860 RefPtr<IRawElementProviderFragment> fragment = 861 MsaaAccessible::GetFrom(target); 862 fragment.forget(aRetVal); 863 return S_OK; 864 } 865 866 STDMETHODIMP 867 uiaRawElmProvider::get_BoundingRectangle(__RPC__out struct UiaRect* aRetVal) { 868 if (!aRetVal) { 869 return E_INVALIDARG; 870 } 871 Accessible* acc = Acc(); 872 if (!acc) { 873 return CO_E_OBJNOTCONNECTED; 874 } 875 LayoutDeviceIntRect rect = acc->Bounds(); 876 aRetVal->left = rect.X(); 877 aRetVal->top = rect.Y(); 878 aRetVal->width = rect.Width(); 879 aRetVal->height = rect.Height(); 880 return S_OK; 881 } 882 883 STDMETHODIMP 884 uiaRawElmProvider::GetEmbeddedFragmentRoots( 885 __RPC__deref_out_opt SAFEARRAY** aRetVal) { 886 if (!aRetVal) { 887 return E_INVALIDARG; 888 } 889 *aRetVal = nullptr; 890 return S_OK; 891 } 892 893 STDMETHODIMP 894 uiaRawElmProvider::SetFocus() { 895 Accessible* acc = Acc(); 896 if (!acc) { 897 return CO_E_OBJNOTCONNECTED; 898 } 899 acc->TakeFocus(); 900 return S_OK; 901 } 902 903 STDMETHODIMP 904 uiaRawElmProvider::get_FragmentRoot( 905 __RPC__deref_out_opt IRawElementProviderFragmentRoot** aRetVal) { 906 if (!aRetVal) { 907 return E_INVALIDARG; 908 } 909 *aRetVal = nullptr; 910 Accessible* acc = Acc(); 911 if (!acc) { 912 return CO_E_OBJNOTCONNECTED; 913 } 914 LocalAccessible* localAcc = acc->AsLocal(); 915 if (!localAcc) { 916 localAcc = acc->AsRemote()->OuterDocOfRemoteBrowser(); 917 if (!localAcc) { 918 return CO_E_OBJNOTCONNECTED; 919 } 920 } 921 MsaaAccessible* msaa = MsaaAccessible::GetFrom(localAcc->RootAccessible()); 922 RefPtr<IRawElementProviderFragmentRoot> fragRoot = 923 static_cast<MsaaRootAccessible*>(msaa); 924 fragRoot.forget(aRetVal); 925 return S_OK; 926 } 927 928 // IInvokeProvider methods 929 930 STDMETHODIMP 931 uiaRawElmProvider::Invoke() { 932 Accessible* acc = Acc(); 933 if (!acc) { 934 return CO_E_OBJNOTCONNECTED; 935 } 936 if (acc->DoAction(0)) { 937 // We don't currently have a way to notify when the action was actually 938 // handled. The UIA documentation says it's okay to fire this immediately if 939 // it "is not possible or practical to wait until the action is complete". 940 ::UiaRaiseAutomationEvent(this, UIA_Invoke_InvokedEventId); 941 } 942 return S_OK; 943 } 944 945 // IToggleProvider methods 946 947 STDMETHODIMP 948 uiaRawElmProvider::Toggle() { 949 Accessible* acc = Acc(); 950 if (!acc) { 951 return CO_E_OBJNOTCONNECTED; 952 } 953 acc->DoAction(0); 954 return S_OK; 955 } 956 957 STDMETHODIMP 958 uiaRawElmProvider::get_ToggleState(__RPC__out enum ToggleState* aRetVal) { 959 if (!aRetVal) { 960 return E_INVALIDARG; 961 } 962 Accessible* acc = Acc(); 963 if (!acc) { 964 return CO_E_OBJNOTCONNECTED; 965 } 966 *aRetVal = ToToggleState(acc->State()); 967 return S_OK; 968 } 969 970 // IExpandCollapseProvider methods 971 972 STDMETHODIMP 973 uiaRawElmProvider::Expand() { 974 Accessible* acc = Acc(); 975 if (!acc) { 976 return CO_E_OBJNOTCONNECTED; 977 } 978 if (acc->State() & states::EXPANDED) { 979 return UIA_E_INVALIDOPERATION; 980 } 981 acc->DoAction(0); 982 return S_OK; 983 } 984 985 STDMETHODIMP 986 uiaRawElmProvider::Collapse() { 987 Accessible* acc = Acc(); 988 if (!acc) { 989 return CO_E_OBJNOTCONNECTED; 990 } 991 if (acc->State() & states::COLLAPSED) { 992 return UIA_E_INVALIDOPERATION; 993 } 994 acc->DoAction(0); 995 return S_OK; 996 } 997 998 STDMETHODIMP 999 uiaRawElmProvider::get_ExpandCollapseState( 1000 __RPC__out enum ExpandCollapseState* aRetVal) { 1001 if (!aRetVal) { 1002 return E_INVALIDARG; 1003 } 1004 Accessible* acc = Acc(); 1005 if (!acc) { 1006 return CO_E_OBJNOTCONNECTED; 1007 } 1008 *aRetVal = ToExpandCollapseState(acc->State()); 1009 return S_OK; 1010 } 1011 1012 // IScrollItemProvider methods 1013 1014 MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP uiaRawElmProvider::ScrollIntoView() { 1015 Accessible* acc = Acc(); 1016 if (!acc) { 1017 return CO_E_OBJNOTCONNECTED; 1018 } 1019 acc->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE); 1020 return S_OK; 1021 } 1022 1023 // IValueProvider methods 1024 1025 STDMETHODIMP 1026 uiaRawElmProvider::SetValue(__RPC__in LPCWSTR aVal) { 1027 Accessible* acc = Acc(); 1028 if (!acc) { 1029 return CO_E_OBJNOTCONNECTED; 1030 } 1031 HyperTextAccessibleBase* ht = acc->AsHyperTextBase(); 1032 if (!ht || !acc->IsTextRole()) { 1033 return UIA_E_INVALIDOPERATION; 1034 } 1035 if (acc->State() & (states::READONLY | states::UNAVAILABLE)) { 1036 return UIA_E_INVALIDOPERATION; 1037 } 1038 nsAutoString text(aVal); 1039 ht->ReplaceText(text); 1040 return S_OK; 1041 } 1042 1043 STDMETHODIMP 1044 uiaRawElmProvider::get_Value(__RPC__deref_out_opt BSTR* aRetVal) { 1045 if (!aRetVal) { 1046 return E_INVALIDARG; 1047 } 1048 *aRetVal = nullptr; 1049 Accessible* acc = Acc(); 1050 if (!acc) { 1051 return CO_E_OBJNOTCONNECTED; 1052 } 1053 nsAutoString value; 1054 acc->Value(value); 1055 if (value.IsEmpty() && acc->IsDoc()) { 1056 // Exposing the URl via the Value pattern doesn't seem to be documented 1057 // anywhere. However, Chromium does it, as does the IA2 -> UIA proxy. 1058 nsAccUtils::DocumentURL(acc, value); 1059 } 1060 *aRetVal = ::SysAllocStringLen(value.get(), value.Length()); 1061 if (!*aRetVal) { 1062 return E_OUTOFMEMORY; 1063 } 1064 return S_OK; 1065 } 1066 1067 STDMETHODIMP 1068 uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) { 1069 if (!aRetVal) { 1070 return E_INVALIDARG; 1071 } 1072 Accessible* acc = Acc(); 1073 if (!acc) { 1074 return CO_E_OBJNOTCONNECTED; 1075 } 1076 *aRetVal = acc->State() & states::READONLY; 1077 return S_OK; 1078 } 1079 1080 // IRangeValueProvider methods 1081 1082 STDMETHODIMP 1083 uiaRawElmProvider::SetValue(double aVal) { 1084 Accessible* acc = Acc(); 1085 if (!acc) { 1086 return CO_E_OBJNOTCONNECTED; 1087 } 1088 if (!acc->SetCurValue(aVal)) { 1089 return UIA_E_INVALIDOPERATION; 1090 } 1091 return S_OK; 1092 } 1093 1094 STDMETHODIMP 1095 uiaRawElmProvider::get_Value(__RPC__out double* aRetVal) { 1096 if (!aRetVal) { 1097 return E_INVALIDARG; 1098 } 1099 Accessible* acc = Acc(); 1100 if (!acc) { 1101 return CO_E_OBJNOTCONNECTED; 1102 } 1103 *aRetVal = acc->CurValue(); 1104 return S_OK; 1105 } 1106 1107 STDMETHODIMP 1108 uiaRawElmProvider::get_Maximum(__RPC__out double* aRetVal) { 1109 if (!aRetVal) { 1110 return E_INVALIDARG; 1111 } 1112 Accessible* acc = Acc(); 1113 if (!acc) { 1114 return CO_E_OBJNOTCONNECTED; 1115 } 1116 *aRetVal = acc->MaxValue(); 1117 return S_OK; 1118 } 1119 1120 STDMETHODIMP 1121 uiaRawElmProvider::get_Minimum( 1122 /* [retval][out] */ __RPC__out double* aRetVal) { 1123 if (!aRetVal) { 1124 return E_INVALIDARG; 1125 } 1126 Accessible* acc = Acc(); 1127 if (!acc) { 1128 return CO_E_OBJNOTCONNECTED; 1129 } 1130 *aRetVal = acc->MinValue(); 1131 return S_OK; 1132 } 1133 1134 STDMETHODIMP 1135 uiaRawElmProvider::get_LargeChange( 1136 /* [retval][out] */ __RPC__out double* aRetVal) { 1137 if (!aRetVal) { 1138 return E_INVALIDARG; 1139 } 1140 Accessible* acc = Acc(); 1141 if (!acc) { 1142 return CO_E_OBJNOTCONNECTED; 1143 } 1144 // We want the change that would occur if the user pressed page up or page 1145 // down. For HTML input elements, this is 10% of the total range unless step 1146 // is larger. See: 1147 // https://searchfox.org/mozilla-central/rev/c7df16ffad1f12a19c81c16bce0b65e4a15304d0/dom/html/HTMLInputElement.cpp#3878 1148 double step = acc->Step(); 1149 double min = acc->MinValue(); 1150 double max = acc->MaxValue(); 1151 if (std::isnan(step) || std::isnan(min) || std::isnan(max)) { 1152 *aRetVal = UnspecifiedNaN<double>(); 1153 } else { 1154 *aRetVal = std::max(step, (max - min) / 10); 1155 } 1156 return S_OK; 1157 } 1158 1159 STDMETHODIMP 1160 uiaRawElmProvider::get_SmallChange( 1161 /* [retval][out] */ __RPC__out double* aRetVal) { 1162 if (!aRetVal) { 1163 return E_INVALIDARG; 1164 } 1165 Accessible* acc = Acc(); 1166 if (!acc) { 1167 return CO_E_OBJNOTCONNECTED; 1168 } 1169 *aRetVal = acc->Step(); 1170 return S_OK; 1171 } 1172 1173 // ISelectionProvider methods 1174 1175 STDMETHODIMP 1176 uiaRawElmProvider::GetSelection(__RPC__deref_out_opt SAFEARRAY** aRetVal) { 1177 if (!aRetVal) { 1178 return E_INVALIDARG; 1179 } 1180 *aRetVal = nullptr; 1181 Accessible* acc = Acc(); 1182 if (!acc) { 1183 return CO_E_OBJNOTCONNECTED; 1184 } 1185 AutoTArray<Accessible*, 10> items; 1186 acc->SelectedItems(&items); 1187 *aRetVal = AccessibleArrayToUiaArray(items); 1188 return S_OK; 1189 } 1190 1191 STDMETHODIMP 1192 uiaRawElmProvider::get_CanSelectMultiple(__RPC__out BOOL* aRetVal) { 1193 if (!aRetVal) { 1194 return E_INVALIDARG; 1195 } 1196 Accessible* acc = Acc(); 1197 if (!acc) { 1198 return CO_E_OBJNOTCONNECTED; 1199 } 1200 *aRetVal = acc->State() & states::MULTISELECTABLE; 1201 return S_OK; 1202 } 1203 1204 STDMETHODIMP 1205 uiaRawElmProvider::get_IsSelectionRequired(__RPC__out BOOL* aRetVal) { 1206 if (!aRetVal) { 1207 return E_INVALIDARG; 1208 } 1209 Accessible* acc = Acc(); 1210 if (!acc) { 1211 return CO_E_OBJNOTCONNECTED; 1212 } 1213 *aRetVal = acc->State() & states::REQUIRED; 1214 return S_OK; 1215 } 1216 1217 // ISelectionItemProvider methods 1218 1219 STDMETHODIMP 1220 uiaRawElmProvider::Select() { 1221 Accessible* acc = Acc(); 1222 if (!acc) { 1223 return CO_E_OBJNOTCONNECTED; 1224 } 1225 if (MustSelectUsingDoAction(acc)) { 1226 acc->DoAction(0); 1227 } else { 1228 acc->TakeSelection(); 1229 } 1230 return S_OK; 1231 } 1232 1233 STDMETHODIMP 1234 uiaRawElmProvider::AddToSelection() { 1235 Accessible* acc = Acc(); 1236 if (!acc) { 1237 return CO_E_OBJNOTCONNECTED; 1238 } 1239 if (MustSelectUsingDoAction(acc)) { 1240 acc->DoAction(0); 1241 } else { 1242 acc->SetSelected(true); 1243 } 1244 return S_OK; 1245 } 1246 1247 STDMETHODIMP 1248 uiaRawElmProvider::RemoveFromSelection() { 1249 Accessible* acc = Acc(); 1250 if (!acc) { 1251 return CO_E_OBJNOTCONNECTED; 1252 } 1253 if (IsRadio(acc)) { 1254 return UIA_E_INVALIDOPERATION; 1255 } 1256 acc->SetSelected(false); 1257 return S_OK; 1258 } 1259 1260 STDMETHODIMP 1261 uiaRawElmProvider::get_IsSelected(__RPC__out BOOL* aRetVal) { 1262 if (!aRetVal) { 1263 return E_INVALIDARG; 1264 } 1265 Accessible* acc = Acc(); 1266 if (!acc) { 1267 return CO_E_OBJNOTCONNECTED; 1268 } 1269 if (IsRadio(acc)) { 1270 *aRetVal = acc->State() & states::CHECKED; 1271 } else { 1272 *aRetVal = acc->State() & states::SELECTED; 1273 } 1274 return S_OK; 1275 } 1276 1277 STDMETHODIMP 1278 uiaRawElmProvider::get_SelectionContainer( 1279 __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { 1280 if (!aRetVal) { 1281 return E_INVALIDARG; 1282 } 1283 *aRetVal = nullptr; 1284 Accessible* acc = Acc(); 1285 if (!acc) { 1286 return CO_E_OBJNOTCONNECTED; 1287 } 1288 Accessible* container = nsAccUtils::GetSelectableContainer(acc, acc->State()); 1289 if (!container) { 1290 return E_FAIL; 1291 } 1292 RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(container); 1293 uia.forget(aRetVal); 1294 return S_OK; 1295 } 1296 1297 // ITextChildProvider methods 1298 1299 STDMETHODIMP 1300 uiaRawElmProvider::get_TextContainer( 1301 __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { 1302 if (!aRetVal) { 1303 return E_INVALIDARG; 1304 } 1305 *aRetVal = nullptr; 1306 Accessible* acc = Acc(); 1307 if (!acc) { 1308 return CO_E_OBJNOTCONNECTED; 1309 } 1310 if (Accessible* container = GetTextContainer(acc)) { 1311 RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(container); 1312 uia.forget(aRetVal); 1313 } 1314 return S_OK; 1315 } 1316 1317 STDMETHODIMP 1318 uiaRawElmProvider::get_TextRange( 1319 __RPC__deref_out_opt ITextRangeProvider** aRetVal) { 1320 if (!aRetVal) { 1321 return E_INVALIDARG; 1322 } 1323 *aRetVal = nullptr; 1324 Accessible* acc = Acc(); 1325 if (!acc) { 1326 return CO_E_OBJNOTCONNECTED; 1327 } 1328 TextLeafRange range = TextLeafRange::FromAccessible(acc); 1329 RefPtr uiaRange = new UiaTextRange(range); 1330 uiaRange.forget(aRetVal); 1331 return S_OK; 1332 } 1333 1334 // Private methods 1335 1336 bool uiaRawElmProvider::IsControl() { 1337 // UIA provides multiple views of the tree: raw, control and content. The 1338 // control and content views should only contain elements which a user cares 1339 // about when navigating. 1340 Accessible* acc = Acc(); 1341 MOZ_ASSERT(acc); 1342 if (acc->IsTextLeaf()) { 1343 // If an ancestor control allows the name to be generated from content, do 1344 // not expose this text leaf as a control. Otherwise, the user will see the 1345 // text twice: once as the label of the control and once for the text leaf. 1346 for (Accessible* ancestor = acc->Parent(); ancestor && !ancestor->IsDoc(); 1347 ancestor = ancestor->Parent()) { 1348 if (nsTextEquivUtils::HasNameRule(ancestor, eNameFromSubtreeRule)) { 1349 return false; 1350 } 1351 } 1352 return true; 1353 } 1354 1355 if (acc->HasNumericValue() || acc->ActionCount() > 0) { 1356 return true; 1357 } 1358 uint64_t state = acc->State(); 1359 if (state & states::FOCUSABLE) { 1360 return true; 1361 } 1362 if (state & states::EDITABLE) { 1363 Accessible* parent = acc->Parent(); 1364 if (parent && !(parent->State() & states::EDITABLE)) { 1365 // This is the root of a rich editable control. 1366 return true; 1367 } 1368 } 1369 1370 // Don't treat generic or text containers as controls except in specific 1371 // cases. 1372 switch (acc->Role()) { 1373 case roles::EMPHASIS: 1374 case roles::MARK: 1375 case roles::PARAGRAPH: 1376 case roles::SECTION: 1377 case roles::STRONG: 1378 case roles::SUBSCRIPT: 1379 case roles::SUPERSCRIPT: 1380 case roles::TEXT: 1381 case roles::TEXT_CONTAINER: { 1382 // If there is a name or a description, treat it as a control. 1383 if (!acc->NameIsEmpty()) { 1384 return true; 1385 } 1386 nsAutoString text; 1387 acc->Description(text); 1388 if (!text.IsEmpty()) { 1389 return true; 1390 } 1391 // If this is the root of a live region, treat it as a control, since 1392 // Narrator won't correctly traverse the live region's content when 1393 // handling changes otherwise. 1394 nsAutoString live; 1395 nsAccUtils::GetLiveRegionSetting(acc, live); 1396 if (!live.IsEmpty()) { 1397 return true; 1398 } 1399 return false; 1400 } 1401 default: 1402 break; 1403 } 1404 1405 return true; 1406 } 1407 1408 long uiaRawElmProvider::GetControlType() const { 1409 Accessible* acc = Acc(); 1410 MOZ_ASSERT(acc); 1411 #define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \ 1412 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \ 1413 nameRule) \ 1414 case roles::_geckoRole: \ 1415 return uiaControlType; \ 1416 break; 1417 switch (acc->Role()) { 1418 #include "RoleMap.h" 1419 } 1420 #undef ROLE 1421 MOZ_CRASH("Unknown role."); 1422 return 0; 1423 } 1424 1425 bool uiaRawElmProvider::HasTogglePattern() { 1426 Accessible* acc = Acc(); 1427 MOZ_ASSERT(acc); 1428 return acc->State() & states::CHECKABLE || 1429 acc->Role() == roles::TOGGLE_BUTTON; 1430 } 1431 1432 bool uiaRawElmProvider::HasExpandCollapsePattern() { 1433 Accessible* acc = Acc(); 1434 MOZ_ASSERT(acc); 1435 return acc->State() & (states::EXPANDABLE | states::HASPOPUP); 1436 } 1437 1438 bool uiaRawElmProvider::HasValuePattern() const { 1439 Accessible* acc = Acc(); 1440 MOZ_ASSERT(acc); 1441 if (acc->HasNumericValue() || acc->IsCombobox() || acc->IsHTMLLink() || 1442 acc->IsTextField() || acc->IsDoc()) { 1443 return true; 1444 } 1445 const nsRoleMapEntry* roleMapEntry = acc->ARIARoleMap(); 1446 return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox); 1447 } 1448 1449 template <class Derived, class Interface> 1450 RefPtr<Interface> uiaRawElmProvider::GetPatternFromDerived() { 1451 // MsaaAccessible inherits from uiaRawElmProvider. Derived 1452 // inherits from MsaaAccessible and Interface. The compiler won't let us 1453 // directly static_cast to Interface, hence the intermediate casts. 1454 auto* msaa = static_cast<MsaaAccessible*>(this); 1455 auto* derived = static_cast<Derived*>(msaa); 1456 return derived; 1457 } 1458 1459 bool uiaRawElmProvider::HasSelectionItemPattern() { 1460 Accessible* acc = Acc(); 1461 MOZ_ASSERT(acc); 1462 // In UIA, radio buttons and radio menu items are exposed as selected or 1463 // unselected. 1464 return acc->State() & states::SELECTABLE || IsRadio(acc); 1465 } 1466 1467 SAFEARRAY* uiaRawElmProvider::AccRelationsToUiaArray( 1468 std::initializer_list<RelationType> aTypes) const { 1469 Accessible* acc = Acc(); 1470 MOZ_ASSERT(acc); 1471 AutoTArray<Accessible*, 10> targets; 1472 for (RelationType type : aTypes) { 1473 Relation rel = acc->RelationByType(type); 1474 while (Accessible* target = rel.Next()) { 1475 targets.AppendElement(target); 1476 } 1477 } 1478 return AccessibleArrayToUiaArray(targets); 1479 } 1480 1481 Accessible* uiaRawElmProvider::GetLabeledBy() const { 1482 // Per the UIA documentation, some control types should never get a value for 1483 // the LabeledBy property. 1484 switch (GetControlType()) { 1485 case UIA_ButtonControlTypeId: 1486 case UIA_CheckBoxControlTypeId: 1487 case UIA_DataItemControlTypeId: 1488 case UIA_MenuControlTypeId: 1489 case UIA_MenuBarControlTypeId: 1490 case UIA_RadioButtonControlTypeId: 1491 case UIA_ScrollBarControlTypeId: 1492 case UIA_SeparatorControlTypeId: 1493 case UIA_StatusBarControlTypeId: 1494 case UIA_TabItemControlTypeId: 1495 case UIA_TextControlTypeId: 1496 case UIA_ToolBarControlTypeId: 1497 case UIA_ToolTipControlTypeId: 1498 case UIA_TreeItemControlTypeId: 1499 return nullptr; 1500 } 1501 1502 Accessible* acc = Acc(); 1503 MOZ_ASSERT(acc); 1504 // Even when LabeledBy is supported, it can only return a single "static text" 1505 // element. 1506 Relation rel = acc->RelationByType(RelationType::LABELLED_BY); 1507 LabelTextLeafRule rule; 1508 while (Accessible* target = rel.Next()) { 1509 // If target were a text leaf, we should return that, but that shouldn't be 1510 // possible because only an element (not a text node) can be the target of a 1511 // relation. 1512 MOZ_ASSERT(!target->IsTextLeaf()); 1513 Pivot pivot(target); 1514 if (Accessible* leaf = pivot.Next(target, rule)) { 1515 return leaf; 1516 } 1517 } 1518 return nullptr; 1519 } 1520 1521 long uiaRawElmProvider::GetLandmarkType() const { 1522 Accessible* acc = Acc(); 1523 MOZ_ASSERT(acc); 1524 nsStaticAtom* landmark = acc->LandmarkRole(); 1525 if (!landmark) { 1526 return 0; 1527 } 1528 if (landmark == nsGkAtoms::form) { 1529 return UIA_FormLandmarkTypeId; 1530 } 1531 if (landmark == nsGkAtoms::main) { 1532 return UIA_MainLandmarkTypeId; 1533 } 1534 if (landmark == nsGkAtoms::navigation) { 1535 return UIA_NavigationLandmarkTypeId; 1536 } 1537 if (landmark == nsGkAtoms::search) { 1538 return UIA_SearchLandmarkTypeId; 1539 } 1540 return UIA_CustomLandmarkTypeId; 1541 } 1542 1543 void uiaRawElmProvider::GetLocalizedLandmarkType(nsAString& aLocalized) const { 1544 Accessible* acc = Acc(); 1545 MOZ_ASSERT(acc); 1546 nsStaticAtom* landmark = acc->LandmarkRole(); 1547 // The system provides strings for landmarks explicitly supported by the UIA 1548 // LandmarkType property; i.e. form, main, navigation and search. We must 1549 // provide strings for landmarks considered custom by UIA. For now, we only 1550 // support landmarks in the core ARIA specification, not other ARIA modules 1551 // such as DPub. 1552 if (landmark == nsGkAtoms::banner || landmark == nsGkAtoms::complementary || 1553 landmark == nsGkAtoms::contentinfo || landmark == nsGkAtoms::region) { 1554 nsAutoString unlocalized; 1555 landmark->ToString(unlocalized); 1556 Accessible::TranslateString(unlocalized, aLocalized); 1557 } 1558 } 1559 1560 long uiaRawElmProvider::GetLiveSetting() const { 1561 Accessible* acc = Acc(); 1562 MOZ_ASSERT(acc); 1563 nsAutoString live; 1564 nsAccUtils::GetLiveRegionSetting(acc, live); 1565 if (live.EqualsLiteral("polite")) { 1566 return LiveSetting::Polite; 1567 } 1568 if (live.EqualsLiteral("assertive")) { 1569 return LiveSetting::Assertive; 1570 } 1571 return LiveSetting::Off; 1572 } 1573 1574 SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) { 1575 // The UIA client framework seems to treat a null value the same as an empty 1576 // array most of the time, but not always. In particular, Narrator breaks if 1577 // ITextRangeProvider::GetChildren returns null instead of an empty array. 1578 // Therefore, don't return null for an empty array. 1579 SAFEARRAY* uias = SafeArrayCreateVector(VT_UNKNOWN, 0, aAccs.Length()); 1580 LONG indices[1] = {0}; 1581 for (Accessible* acc : aAccs) { 1582 // SafeArrayPutElement calls AddRef on the element, so we use a raw pointer 1583 // here. 1584 IRawElementProviderSimple* uia = MsaaAccessible::GetFrom(acc); 1585 SafeArrayPutElement(uias, indices, uia); 1586 ++indices[0]; 1587 } 1588 return uias; 1589 } 1590 1591 const UiaRegistrations& a11y::GetUiaRegistrations() { 1592 static UiaRegistrations sRegistrations = {}; 1593 static bool sRegistered = false; 1594 if (sRegistered) { 1595 return sRegistrations; 1596 } 1597 RefPtr<IUIAutomationRegistrar> registrar; 1598 if (FAILED(CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr, 1599 CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar, 1600 getter_AddRefs(registrar)))) { 1601 return sRegistrations; 1602 } 1603 UIAutomationPropertyInfo actionsInfo = { 1604 // https://w3c.github.io/core-aam/#ariaActions 1605 // {8C787AC3-0405-4C94-AC09-7A56A173F7EF} 1606 {0x8C787AC3, 1607 0x0405, 1608 0x4C94, 1609 {0xAC, 0x09, 0x7A, 0x56, 0xA1, 0x73, 0xF7, 0xEF}}, 1610 L"AccessibleActions", 1611 UIAutomationType_ElementArray}; 1612 registrar->RegisterProperty(&actionsInfo, &sRegistrations.mAccessibleActions); 1613 sRegistered = true; 1614 return sRegistrations; 1615 }