summaryrefslogtreecommitdiffstats
path: root/accessible/windows/uia/uiaRawElmProvider.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
commita90a5cba08fdf6c0ceb95101c275108a152a3aed (patch)
tree532507288f3defd7f4dcf1af49698bcb76034855 /accessible/windows/uia/uiaRawElmProvider.cpp
parentAdding debian version 126.0.1-1. (diff)
downloadfirefox-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.cpp594
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;
+}