diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:37 +0000 |
commit | a90a5cba08fdf6c0ceb95101c275108a152a3aed (patch) | |
tree | 532507288f3defd7f4dcf1af49698bcb76034855 /accessible/windows/uia/uiaRawElmProvider.cpp | |
parent | Adding debian version 126.0.1-1. (diff) | |
download | firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/windows/uia/uiaRawElmProvider.cpp')
-rw-r--r-- | accessible/windows/uia/uiaRawElmProvider.cpp | 594 |
1 files changed, 581 insertions, 13 deletions
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp index c022e40cef..82726bb9aa 100644 --- a/accessible/windows/uia/uiaRawElmProvider.cpp +++ b/accessible/windows/uia/uiaRawElmProvider.cpp @@ -11,7 +11,10 @@ #include "AccAttributes.h" #include "AccessibleWrap.h" +#include "ApplicationAccessible.h" #include "ARIAMap.h" +#include "ia2AccessibleTable.h" +#include "ia2AccessibleTableCell.h" #include "LocalAccessible-inl.h" #include "mozilla/a11y/RemoteAccessible.h" #include "mozilla/StaticPrefs_accessibility.h" @@ -19,12 +22,25 @@ #include "MsaaRootAccessible.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" +#include "nsIAccessiblePivot.h" #include "nsTextEquivUtils.h" +#include "Pivot.h" +#include "Relation.h" #include "RootAccessible.h" using namespace mozilla; using namespace mozilla::a11y; +#ifdef __MINGW32__ +// These constants are missing in mingw-w64. This code should be removed once +// we update to a version which includes them. +const long UIA_CustomLandmarkTypeId = 80000; +const long UIA_FormLandmarkTypeId = 80001; +const long UIA_MainLandmarkTypeId = 80002; +const long UIA_NavigationLandmarkTypeId = 80003; +const long UIA_SearchLandmarkTypeId = 80004; +#endif // __MINGW32__ + // Helper functions static ToggleState ToToggleState(uint64_t aState) { @@ -51,6 +67,34 @@ static ExpandCollapseState ToExpandCollapseState(uint64_t aState) { return ExpandCollapseState_LeafNode; } +static bool IsRadio(Accessible* aAcc) { + role r = aAcc->Role(); + return r == roles::RADIOBUTTON || r == roles::RADIO_MENU_ITEM; +} + +// Used to search for a text leaf descendant for the LabeledBy property. +class LabelTextLeafRule : public PivotRule { + public: + virtual uint16_t Match(Accessible* aAcc) override { + if (aAcc->IsTextLeaf()) { + nsAutoString name; + aAcc->Name(name); + if (name.IsEmpty() || name.EqualsLiteral(" ")) { + // An empty or white space text leaf isn't useful as a label. + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + if (!nsTextEquivUtils::HasNameRule(aAcc, eNameFromSubtreeIfReqRule)) { + // Don't descend into things that can't be used as label content; e.g. + // text boxes. + return nsIAccessibleTraversalRule::FILTER_IGNORE | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } +}; + //////////////////////////////////////////////////////////////////////////////// // uiaRawElmProvider //////////////////////////////////////////////////////////////////////////////// @@ -84,12 +128,32 @@ void uiaRawElmProvider::RaiseUiaEventForGeckoEvent(Accessible* aAcc, case nsIAccessibleEvent::EVENT_NAME_CHANGE: property = UIA_NamePropertyId; break; + case nsIAccessibleEvent::EVENT_SELECTION: + ::UiaRaiseAutomationEvent(uia, UIA_SelectionItem_ElementSelectedEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_ADD: + ::UiaRaiseAutomationEvent( + uia, UIA_SelectionItem_ElementAddedToSelectionEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: + ::UiaRaiseAutomationEvent( + uia, UIA_SelectionItem_ElementRemovedFromSelectionEventId); + return; + case nsIAccessibleEvent::EVENT_SELECTION_WITHIN: + ::UiaRaiseAutomationEvent(uia, UIA_Selection_InvalidatedEventId); + return; case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: property = UIA_ValueValuePropertyId; newVal.vt = VT_BSTR; uia->get_Value(&newVal.bstrVal); gotNewVal = true; break; + case nsIAccessibleEvent::EVENT_VALUE_CHANGE: + property = UIA_RangeValueValuePropertyId; + newVal.vt = VT_R8; + uia->get_Value(&newVal.dblVal); + gotNewVal = true; + break; } if (property && ::UiaClientsAreListening()) { // We can't get the old value. Thankfully, clients don't seem to need it. @@ -117,6 +181,13 @@ void uiaRawElmProvider::RaiseUiaEventForStateChange(Accessible* aAcc, _variant_t newVal; switch (aState) { case states::CHECKED: + if (aEnabled && IsRadio(aAcc)) { + ::UiaRaiseAutomationEvent(uia, + UIA_SelectionItem_ElementSelectedEventId); + return; + } + // For other checkable things, the Toggle pattern is used. + [[fallthrough]]; case states::MIXED: case states::PRESSED: property = UIA_ToggleToggleStatePropertyId; @@ -161,8 +232,14 @@ uiaRawElmProvider::QueryInterface(REFIID aIid, void** aInterface) { *aInterface = static_cast<IExpandCollapseProvider*>(this); } else if (aIid == IID_IInvokeProvider) { *aInterface = static_cast<IInvokeProvider*>(this); + } else if (aIid == IID_IRangeValueProvider) { + *aInterface = static_cast<IRangeValueProvider*>(this); } else if (aIid == IID_IScrollItemProvider) { *aInterface = static_cast<IScrollItemProvider*>(this); + } else if (aIid == IID_ISelectionItemProvider) { + *aInterface = static_cast<ISelectionItemProvider*>(this); + } else if (aIid == IID_ISelectionProvider) { + *aInterface = static_cast<ISelectionProvider*>(this); } else if (aIid == IID_IToggleProvider) { *aInterface = static_cast<IToggleProvider*>(this); } else if (aIid == IID_IValueProvider) { @@ -248,9 +325,7 @@ uiaRawElmProvider::get_ProviderOptions( __RPC__out enum ProviderOptions* aOptions) { if (!aOptions) return E_INVALIDARG; - *aOptions = static_cast<enum ProviderOptions>( - ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading | - ProviderOptions_HasNativeIAccessible); + *aOptions = kProviderOptions; return S_OK; } @@ -270,21 +345,78 @@ uiaRawElmProvider::GetPatternProvider( expand.forget(aPatternProvider); } return S_OK; + case UIA_GridPatternId: + if (acc->IsTable()) { + auto grid = GetPatternFromDerived<ia2AccessibleTable, IGridProvider>(); + grid.forget(aPatternProvider); + } + return S_OK; + case UIA_GridItemPatternId: + if (acc->IsTableCell()) { + auto item = + GetPatternFromDerived<ia2AccessibleTableCell, IGridItemProvider>(); + item.forget(aPatternProvider); + } + return S_OK; case UIA_InvokePatternId: // Per the UIA documentation, we should only expose the Invoke pattern "if // the same behavior is not exposed through another control pattern // provider". if (acc->ActionCount() > 0 && !HasTogglePattern() && - !HasExpandCollapsePattern()) { + !HasExpandCollapsePattern() && !HasSelectionItemPattern()) { RefPtr<IInvokeProvider> invoke = this; invoke.forget(aPatternProvider); } return S_OK; + case UIA_RangeValuePatternId: + if (acc->HasNumericValue()) { + RefPtr<IValueProvider> value = this; + value.forget(aPatternProvider); + } + return S_OK; case UIA_ScrollItemPatternId: { RefPtr<IScrollItemProvider> scroll = this; scroll.forget(aPatternProvider); return S_OK; } + case UIA_SelectionItemPatternId: + if (HasSelectionItemPattern()) { + RefPtr<ISelectionItemProvider> item = this; + item.forget(aPatternProvider); + } + return S_OK; + case UIA_SelectionPatternId: + // According to the UIA documentation, radio button groups should support + // the Selection pattern. However: + // 1. The Core AAM spec doesn't specify the Selection pattern for + // the radiogroup role. + // 2. HTML radio buttons might not be contained by a dedicated group. + // 3. Chromium exposes the Selection pattern on radio groups, but it + // doesn't expose any selected items, even when there is a checked radio + // child. + // 4. Radio menu items are similar to radio buttons and all the above + // also applies to menus. + // For now, we don't support the Selection pattern for radio groups or + // menus, only for list boxes, tab lists, etc. + if (acc->IsSelect()) { + RefPtr<ISelectionProvider> selection = this; + selection.forget(aPatternProvider); + } + return S_OK; + case UIA_TablePatternId: + if (acc->IsTable()) { + auto table = + GetPatternFromDerived<ia2AccessibleTable, ITableProvider>(); + table.forget(aPatternProvider); + } + return S_OK; + case UIA_TableItemPatternId: + if (acc->IsTableCell()) { + auto item = + GetPatternFromDerived<ia2AccessibleTableCell, ITableItemProvider>(); + item.forget(aPatternProvider); + } + return S_OK; case UIA_TogglePatternId: if (HasTogglePattern()) { RefPtr<IToggleProvider> toggle = this; @@ -349,19 +481,19 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, break; } - // ARIA Role / shortcut case UIA_AriaRolePropertyId: { - nsAutoString xmlRoles; - - RefPtr<AccAttributes> attributes = acc->Attributes(); - attributes->GetAttribute(nsGkAtoms::xmlroles, xmlRoles); - - if (!xmlRoles.IsEmpty()) { + nsAutoString role; + if (acc->HasARIARole()) { + RefPtr<AccAttributes> attributes = acc->Attributes(); + attributes->GetAttribute(nsGkAtoms::xmlroles, role); + } else if (nsStaticAtom* computed = acc->ComputedARIARole()) { + computed->ToString(role); + } + if (!role.IsEmpty()) { aPropertyValue->vt = VT_BSTR; - aPropertyValue->bstrVal = ::SysAllocString(xmlRoles.get()); + aPropertyValue->bstrVal = ::SysAllocString(role.get()); return S_OK; } - break; } @@ -415,11 +547,57 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, break; } + case UIA_ClassNamePropertyId: { + nsAutoString className; + acc->DOMNodeClass(className); + if (!className.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(className.get()); + return S_OK; + } + break; + } + + case UIA_ControllerForPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray( + {RelationType::CONTROLLER_FOR, RelationType::ERRORMSG}); + return S_OK; + case UIA_ControlTypePropertyId: aPropertyValue->vt = VT_I4; aPropertyValue->lVal = GetControlType(); break; + case UIA_DescribedByPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray( + {RelationType::DESCRIBED_BY, RelationType::DETAILS}); + return S_OK; + + case UIA_FlowsFromPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = + AccRelationsToUiaArray({RelationType::FLOWS_FROM}); + return S_OK; + + case UIA_FlowsToPropertyId: + aPropertyValue->vt = VT_UNKNOWN | VT_ARRAY; + aPropertyValue->parray = AccRelationsToUiaArray({RelationType::FLOWS_TO}); + return S_OK; + + case UIA_FrameworkIdPropertyId: + if (ApplicationAccessible* app = ApplicationAcc()) { + nsAutoString name; + app->PlatformName(name); + if (!name.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(name.get()); + return S_OK; + } + } + break; + case UIA_FullDescriptionPropertyId: { nsAutoString desc; acc->Description(desc); @@ -459,6 +637,39 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, (acc->State() & states::FOCUSABLE) ? VARIANT_TRUE : VARIANT_FALSE; return S_OK; + case UIA_LabeledByPropertyId: + if (Accessible* target = GetLabeledBy()) { + aPropertyValue->vt = VT_UNKNOWN; + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(target); + uia.forget(&aPropertyValue->punkVal); + return S_OK; + } + break; + + case UIA_LandmarkTypePropertyId: + if (long type = GetLandmarkType()) { + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = type; + return S_OK; + } + break; + + case UIA_LevelPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().level; + return S_OK; + + case UIA_LocalizedLandmarkTypePropertyId: { + nsAutoString landmark; + GetLocalizedLandmarkType(landmark); + if (!landmark.IsEmpty()) { + aPropertyValue->vt = VT_BSTR; + aPropertyValue->bstrVal = ::SysAllocString(landmark.get()); + return S_OK; + } + break; + } + case UIA_NamePropertyId: { nsAutoString name; acc->Name(name); @@ -469,6 +680,16 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId, } break; } + + case UIA_PositionInSetPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().posInSet; + return S_OK; + + case UIA_SizeOfSetPropertyId: + aPropertyValue->vt = VT_I4; + aPropertyValue->lVal = acc->GroupPosition().setSize; + return S_OK; } return S_OK; @@ -749,6 +970,223 @@ uiaRawElmProvider::get_IsReadOnly(__RPC__out BOOL* aRetVal) { return S_OK; } +// IRangeValueProvider methods + +STDMETHODIMP +uiaRawElmProvider::SetValue(double aVal) { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (!acc->SetCurValue(aVal)) { + return UIA_E_INVALIDOPERATION; + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Value(__RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->CurValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Maximum(__RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->MaxValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_Minimum( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->MinValue(); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_LargeChange( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + // We want the change that would occur if the user pressed page up or page + // down. For HTML input elements, this is 10% of the total range unless step + // is larger. See: + // https://searchfox.org/mozilla-central/rev/c7df16ffad1f12a19c81c16bce0b65e4a15304d0/dom/html/HTMLInputElement.cpp#3878 + double step = acc->Step(); + double min = acc->MinValue(); + double max = acc->MaxValue(); + if (std::isnan(step) || std::isnan(min) || std::isnan(max)) { + *aRetVal = UnspecifiedNaN<double>(); + } else { + *aRetVal = std::max(step, (max - min) / 10); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_SmallChange( + /* [retval][out] */ __RPC__out double* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->Step(); + return S_OK; +} + +// ISelectionProvider methods + +STDMETHODIMP +uiaRawElmProvider::GetSelection(__RPC__deref_out_opt SAFEARRAY** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + AutoTArray<Accessible*, 10> items; + acc->SelectedItems(&items); + *aRetVal = AccessibleArrayToUiaArray(items); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_CanSelectMultiple(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->State() & states::MULTISELECTABLE; + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_IsSelectionRequired(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + *aRetVal = acc->State() & states::REQUIRED; + return S_OK; +} + +// ISelectionItemProvider methods + +STDMETHODIMP +uiaRawElmProvider::Select() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + acc->DoAction(0); + } else { + acc->TakeSelection(); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::AddToSelection() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + acc->DoAction(0); + } else { + acc->SetSelected(true); + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::RemoveFromSelection() { + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + return UIA_E_INVALIDOPERATION; + } + acc->SetSelected(false); + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_IsSelected(__RPC__out BOOL* aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + if (IsRadio(acc)) { + *aRetVal = acc->State() & states::CHECKED; + } else { + *aRetVal = acc->State() & states::SELECTED; + } + return S_OK; +} + +STDMETHODIMP +uiaRawElmProvider::get_SelectionContainer( + __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) { + if (!aRetVal) { + return E_INVALIDARG; + } + *aRetVal = nullptr; + Accessible* acc = Acc(); + if (!acc) { + return CO_E_OBJNOTCONNECTED; + } + Accessible* container = nsAccUtils::GetSelectableContainer(acc, acc->State()); + if (!container) { + return E_FAIL; + } + RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(container); + uia.forget(aRetVal); + return S_OK; +} + // Private methods bool uiaRawElmProvider::IsControl() { @@ -854,3 +1292,133 @@ bool uiaRawElmProvider::HasValuePattern() const { const nsRoleMapEntry* roleMapEntry = acc->ARIARoleMap(); return roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox); } + +template <class Derived, class Interface> +RefPtr<Interface> uiaRawElmProvider::GetPatternFromDerived() { + // MsaaAccessible inherits from uiaRawElmProvider. Derived + // inherits from MsaaAccessible and Interface. The compiler won't let us + // directly static_cast to Interface, hence the intermediate casts. + auto* msaa = static_cast<MsaaAccessible*>(this); + auto* derived = static_cast<Derived*>(msaa); + return derived; +} + +bool uiaRawElmProvider::HasSelectionItemPattern() { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + // In UIA, radio buttons and radio menu items are exposed as selected or + // unselected. + return acc->State() & states::SELECTABLE || IsRadio(acc); +} + +SAFEARRAY* uiaRawElmProvider::AccRelationsToUiaArray( + std::initializer_list<RelationType> aTypes) const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + AutoTArray<Accessible*, 10> targets; + for (RelationType type : aTypes) { + Relation rel = acc->RelationByType(type); + while (Accessible* target = rel.Next()) { + targets.AppendElement(target); + } + } + return AccessibleArrayToUiaArray(targets); +} + +Accessible* uiaRawElmProvider::GetLabeledBy() const { + // Per the UIA documentation, some control types should never get a value for + // the LabeledBy property. + switch (GetControlType()) { + case UIA_ButtonControlTypeId: + case UIA_CheckBoxControlTypeId: + case UIA_DataItemControlTypeId: + case UIA_MenuControlTypeId: + case UIA_MenuBarControlTypeId: + case UIA_RadioButtonControlTypeId: + case UIA_ScrollBarControlTypeId: + case UIA_SeparatorControlTypeId: + case UIA_StatusBarControlTypeId: + case UIA_TabItemControlTypeId: + case UIA_TextControlTypeId: + case UIA_ToolBarControlTypeId: + case UIA_ToolTipControlTypeId: + case UIA_TreeItemControlTypeId: + return nullptr; + } + + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + // Even when LabeledBy is supported, it can only return a single "static text" + // element. + Relation rel = acc->RelationByType(RelationType::LABELLED_BY); + LabelTextLeafRule rule; + while (Accessible* target = rel.Next()) { + // If target were a text leaf, we should return that, but that shouldn't be + // possible because only an element (not a text node) can be the target of a + // relation. + MOZ_ASSERT(!target->IsTextLeaf()); + Pivot pivot(target); + if (Accessible* leaf = pivot.Next(target, rule)) { + return leaf; + } + } + return nullptr; +} + +long uiaRawElmProvider::GetLandmarkType() const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + nsStaticAtom* landmark = acc->LandmarkRole(); + if (!landmark) { + return 0; + } + if (landmark == nsGkAtoms::form) { + return UIA_FormLandmarkTypeId; + } + if (landmark == nsGkAtoms::main) { + return UIA_MainLandmarkTypeId; + } + if (landmark == nsGkAtoms::navigation) { + return UIA_NavigationLandmarkTypeId; + } + if (landmark == nsGkAtoms::search) { + return UIA_SearchLandmarkTypeId; + } + return UIA_CustomLandmarkTypeId; +} + +void uiaRawElmProvider::GetLocalizedLandmarkType(nsAString& aLocalized) const { + Accessible* acc = Acc(); + MOZ_ASSERT(acc); + nsStaticAtom* landmark = acc->LandmarkRole(); + // The system provides strings for landmarks explicitly supported by the UIA + // LandmarkType property; i.e. form, main, navigation and search. We must + // provide strings for landmarks considered custom by UIA. For now, we only + // support landmarks in the core ARIA specification, not other ARIA modules + // such as DPub. + if (landmark == nsGkAtoms::banner || landmark == nsGkAtoms::complementary || + landmark == nsGkAtoms::contentinfo || landmark == nsGkAtoms::region) { + nsAutoString unlocalized; + landmark->ToString(unlocalized); + Accessible::TranslateString(unlocalized, aLocalized); + } +} + +SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) { + if (aAccs.IsEmpty()) { + // The UIA documentation is unclear about this, but the UIA client + // framework seems to treat a null value the same as an empty array. This + // is also what Chromium does. + return nullptr; + } + SAFEARRAY* uias = SafeArrayCreateVector(VT_UNKNOWN, 0, aAccs.Length()); + LONG indices[1] = {0}; + for (Accessible* acc : aAccs) { + // SafeArrayPutElement calls AddRef on the element, so we use a raw pointer + // here. + IRawElementProviderSimple* uia = MsaaAccessible::GetFrom(acc); + SafeArrayPutElement(uias, indices, uia); + ++indices[0]; + } + return uias; +} |