tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }