summaryrefslogtreecommitdiffstats
path: root/accessible
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
commit8dd16259287f58f9273002717ec4d27e97127719 (patch)
tree3863e62a53829a84037444beab3abd4ed9dfc7d0 /accessible
parentReleasing progress-linux version 126.0.1-1~progress7.99u1. (diff)
downloadfirefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz
firefox-8dd16259287f58f9273002717ec4d27e97127719.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible')
-rw-r--r--accessible/base/ARIAMap.cpp109
-rw-r--r--accessible/base/ARIAMap.h5
-rw-r--r--accessible/base/AccTypes.h1
-rw-r--r--accessible/base/nsAccessibilityService.cpp168
-rw-r--r--accessible/base/nsCoreUtils.cpp26
-rw-r--r--accessible/base/nsCoreUtils.h5
-rw-r--r--accessible/basetypes/Accessible.cpp43
-rw-r--r--accessible/basetypes/Accessible.h7
-rw-r--r--accessible/docs/index.rst2
-rw-r--r--accessible/generic/LocalAccessible.cpp44
-rw-r--r--accessible/generic/LocalAccessible.h2
-rw-r--r--accessible/ipc/DocAccessibleChild.h4
-rw-r--r--accessible/ipc/RemoteAccessible.cpp9
-rw-r--r--accessible/ipc/RemoteAccessible.h2
-rw-r--r--accessible/mac/AccessibleWrap.mm3
-rw-r--r--accessible/mac/MOXWebAreaAccessible.mm2
-rw-r--r--accessible/mac/mozActionElements.h29
-rw-r--r--accessible/mac/mozActionElements.mm41
-rw-r--r--accessible/tests/browser/e10s/browser.toml1
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js13
-rw-r--r--accessible/tests/browser/e10s/head.js5
-rw-r--r--accessible/tests/browser/mac/browser_range.js54
-rw-r--r--accessible/tests/browser/python_runner_wsh.py4
-rw-r--r--accessible/tests/browser/selectable/browser_test_aria_select.js23
-rw-r--r--accessible/tests/browser/windows/uia/browser.toml7
-rw-r--r--accessible/tests/browser/windows/uia/browser_generalProps.js350
-rw-r--r--accessible/tests/browser/windows/uia/browser_gridPatterns.js161
-rw-r--r--accessible/tests/browser/windows/uia/browser_relationProps.js143
-rw-r--r--accessible/tests/browser/windows/uia/browser_selectionPatterns.js226
-rw-r--r--accessible/tests/browser/windows/uia/browser_simplePatterns.js97
-rw-r--r--accessible/tests/browser/windows/uia/head.js14
-rw-r--r--accessible/tests/mochitest/attributes.js5
-rw-r--r--accessible/tests/mochitest/role/test_dpub_aria.html6
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.cpp14
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.h2
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.cpp2
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.h2
-rw-r--r--accessible/windows/msaa/IUnknownImpl.h42
-rw-r--r--accessible/windows/msaa/LazyInstantiator.cpp82
-rw-r--r--accessible/windows/msaa/LazyInstantiator.h27
-rw-r--r--accessible/windows/msaa/Platform.cpp1
-rw-r--r--accessible/windows/uia/UiaGrid.cpp151
-rw-r--r--accessible/windows/uia/UiaGrid.h52
-rw-r--r--accessible/windows/uia/UiaGridItem.cpp130
-rw-r--r--accessible/windows/uia/UiaGridItem.h51
-rw-r--r--accessible/windows/uia/moz.build3
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.cpp594
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.h75
-rw-r--r--accessible/xpcom/xpcAccessibilityService.cpp3
49 files changed, 2576 insertions, 266 deletions
diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp
index d53592acf0..a557405497 100644
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -256,7 +256,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-acknowledgments
@@ -286,7 +286,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-backlink
@@ -296,7 +296,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eJumpAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::LINKED
},
{ // doc-biblioentry
@@ -306,7 +306,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::READONLY
},
{ // doc-bibliography
@@ -316,7 +316,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-biblioref
@@ -326,7 +326,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eJumpAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::LINKED
},
{ // doc-chapter
@@ -336,7 +336,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-colophon
@@ -346,7 +346,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-conclusion
@@ -356,7 +356,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-cover
@@ -366,7 +366,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-credit
@@ -376,7 +376,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-credits
@@ -386,7 +386,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-dedication
@@ -396,7 +396,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-endnote
@@ -406,7 +406,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::READONLY
},
{ // doc-endnotes
@@ -416,7 +416,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-epigraph
@@ -426,7 +426,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-epilogue
@@ -436,7 +436,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-errata
@@ -446,17 +446,17 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-example
nsGkAtoms::docExample,
- roles::SECTION,
+ roles::FIGURE,
kUseMapRole,
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-footnote
@@ -466,7 +466,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-foreword
@@ -476,7 +476,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-glossary
@@ -486,7 +486,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-glossref
@@ -496,7 +496,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eJumpAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::LINKED
},
{ // doc-index
@@ -506,7 +506,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-introduction
@@ -516,7 +516,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-noteref
@@ -526,7 +526,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eJumpAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
states::LINKED
},
{ // doc-notice
@@ -536,7 +536,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-pagebreak
@@ -546,7 +546,27 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
+ kNoReqStates
+ },
+ { // doc-pagefooter
+ nsGkAtoms::docPagefooter,
+ roles::SECTION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eDPub,
+ kNoReqStates
+ },
+ { // doc-pageheader
+ nsGkAtoms::docPageheader,
+ roles::SECTION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eDPub,
kNoReqStates
},
{ // doc-pagelist
@@ -556,7 +576,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-part
@@ -566,7 +586,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-preface
@@ -576,7 +596,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-prologue
@@ -586,7 +606,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // doc-pullquote
@@ -596,7 +616,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-qna
@@ -606,7 +626,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-subtitle
@@ -616,7 +636,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-tip
@@ -626,7 +646,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- kGenericAccType,
+ eDPub,
kNoReqStates
},
{ // doc-toc
@@ -636,7 +656,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
eNoValue,
eNoAction,
eNoLiveAttr,
- eLandmark,
+ eDPub | eLandmark,
kNoReqStates
},
{ // document
@@ -1589,6 +1609,19 @@ bool aria::HasDefinedARIAHidden(nsIContent* aContent) {
eCaseMatters);
}
+const nsRoleMapEntry* aria::GetRoleMap(const nsStaticAtom* aAriaRole) {
+ const nsDependentAtomString role(aAriaRole);
+ auto comparator = [&role](const nsRoleMapEntry& aEntry) {
+ return Compare(role, aEntry.ARIARoleString());
+ };
+ size_t idx;
+ if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps), comparator,
+ &idx)) {
+ return GetRoleMapFromIndex(idx);
+ }
+ return nullptr;
+}
+
////////////////////////////////////////////////////////////////////////////////
// AttrIterator class
diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h
index 58a96b7112..2fa3ac80d4 100644
--- a/accessible/base/ARIAMap.h
+++ b/accessible/base/ARIAMap.h
@@ -305,6 +305,11 @@ uint8_t AttrCharacteristicsFor(nsAtom* aAtom);
bool HasDefinedARIAHidden(nsIContent* aContent);
/**
+ * Get the role map entry for a given ARIA role.
+ */
+const nsRoleMapEntry* GetRoleMap(const nsStaticAtom* aAriaRole);
+
+/**
* Represents a simple enumerator for iterating through ARIA attributes
* exposed as object attributes on a given accessible.
*/
diff --git a/accessible/base/AccTypes.h b/accessible/base/AccTypes.h
index 3e9d88e486..76fdaae2d7 100644
--- a/accessible/base/AccTypes.h
+++ b/accessible/base/AccTypes.h
@@ -87,6 +87,7 @@ enum AccGenericType {
eText = 1 << 14,
eNumericValue = 1 << 15,
eActionable = 1 << 16, // This is for remote accessibles
+ eDPub = 1 << 17,
eLastAccGenericType = eActionable,
eAllGenericTypes = (eLastAccGenericType << 1) - 1
diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
index 615af596a7..7b5f127672 100644
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -12,6 +12,7 @@
#include "DocAccessible-inl.h"
#include "DocAccessibleChild.h"
#include "FocusManager.h"
+#include "mozilla/FocusModel.h"
#include "HTMLCanvasAccessible.h"
#include "HTMLElementAccessibles.h"
#include "HTMLImageMapAccessible.h"
@@ -136,48 +137,6 @@ static LocalAccessible* MaybeCreateSpecificARIAAccessible(
}
/**
- * Return true if the element has an attribute (ARIA, title, or relation) that
- * requires the creation of an Accessible for the element.
- */
-static bool AttributesMustBeAccessible(nsIContent* aContent,
- DocAccessible* aDocument) {
- if (aContent->IsElement()) {
- uint32_t attrCount = aContent->AsElement()->GetAttrCount();
- for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
- const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx);
- if (attr->NamespaceEquals(kNameSpaceID_None)) {
- nsAtom* attrAtom = attr->Atom();
- if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) {
- // If the author provided a title on an element that would not
- // be accessible normally, assume an intent and make it accessible.
- return true;
- }
-
- nsDependentAtomString attrStr(attrAtom);
- if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA
-
- // A global state or a property and in case of token defined.
- uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
- if ((attrFlags & ATTR_GLOBAL) &&
- (!(attrFlags & ATTR_VALTOKEN) ||
- nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
- return true;
- }
- }
- }
-
- // If the given ID is referred by relation attribute then create an
- // Accessible for it.
- nsAutoString id;
- if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
- return aDocument->IsDependentID(aContent->AsElement(), id);
- }
- }
-
- return false;
-}
-
-/**
* Return true if the element must be a generic Accessible, even if it has been
* marked presentational with role="presentation", etc. MustBeAccessible causes
* an Accessible to be created as if it weren't marked presentational at all;
@@ -224,19 +183,53 @@ static bool MustBeGenericAccessible(nsIContent* aContent,
* Return true if the element must be accessible.
*/
static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
- nsIFrame* frame = aContent->GetPrimaryFrame();
- MOZ_ASSERT(frame);
- // This document might be invisible when it first loads. Therefore, we must
- // check focusability irrespective of visibility here. Otherwise, we might not
- // create Accessibles for some focusable elements; e.g. a span with only a
- // tabindex. Elements that are invisible within this document are excluded
- // earlier in CreateAccessible.
- if (frame->IsFocusable(/* aWithMouse */ false,
- /* aCheckVisibility */ false)) {
- return true;
+ if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
+ // This document might be invisible when it first loads. Therefore, we must
+ // check focusability irrespective of visibility here. Otherwise, we might
+ // not create Accessibles for some focusable elements; e.g. a span with only
+ // a tabindex. Elements that are invisible within this document are excluded
+ // earlier in CreateAccessible.
+ if (frame->IsFocusable(IsFocusableFlags::IgnoreVisibility)) {
+ return true;
+ }
+ }
+
+ // Return true if the element has an attribute (ARIA, title, or relation) that
+ // requires the creation of an Accessible for the element.
+ if (aContent->IsElement()) {
+ uint32_t attrCount = aContent->AsElement()->GetAttrCount();
+ for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
+ const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx);
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsAtom* attrAtom = attr->Atom();
+ if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) {
+ // If the author provided a title on an element that would not
+ // be accessible normally, assume an intent and make it accessible.
+ return true;
+ }
+
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA
+
+ // A global state or a property and in case of token defined.
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
+ if ((attrFlags & ATTR_GLOBAL) &&
+ (!(attrFlags & ATTR_VALTOKEN) ||
+ nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
+ return true;
+ }
+ }
+ }
+
+ // If the given ID is referred by relation attribute then create an
+ // Accessible for it.
+ nsAutoString id;
+ if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
+ return aDocument->IsDependentID(aContent->AsElement(), id);
+ }
}
- return AttributesMustBeAccessible(aContent, aDocument);
+ return false;
}
bool nsAccessibilityService::ShouldCreateImgAccessible(
@@ -294,6 +287,38 @@ static bool MustSVGElementBeAccessible(nsIContent* aContent,
}
/**
+ * Return an accessible for the content if the SVG element requires the creation
+ * of an Accessible.
+ */
+static RefPtr<LocalAccessible> MaybeCreateSVGAccessible(
+ nsIContent* aContent, DocAccessible* aDocument) {
+ if (aContent->IsSVGGeometryElement() ||
+ aContent->IsSVGElement(nsGkAtoms::image)) {
+ // Shape elements: rect, circle, ellipse, line, path, polygon, and polyline.
+ // 'use' and 'text' graphic elements require special support.
+ if (MustSVGElementBeAccessible(aContent, aDocument)) {
+ return new EnumRoleAccessible<roles::GRAPHIC>(aContent, aDocument);
+ }
+ } else if (aContent->IsSVGElement(nsGkAtoms::text)) {
+ return new HyperTextAccessible(aContent->AsElement(), aDocument);
+ } else if (aContent->IsSVGElement(nsGkAtoms::svg)) {
+ // An <svg> element could contain <foreignObject>, which contains HTML but
+ // does not normally create its own Accessible. This means that the <svg>
+ // Accessible could have TextLeafAccessible children, so it must be a
+ // HyperTextAccessible.
+ return new EnumRoleHyperTextAccessible<roles::DIAGRAM>(aContent, aDocument);
+ } else if (aContent->IsSVGElement(nsGkAtoms::g) &&
+ MustSVGElementBeAccessible(aContent, aDocument)) {
+ // <g> can also contain <foreignObject>.
+ return new EnumRoleHyperTextAccessible<roles::GROUPING>(aContent,
+ aDocument);
+ } else if (aContent->IsSVGElement(nsGkAtoms::a)) {
+ return new HTMLLinkAccessible(aContent, aDocument);
+ }
+ return nullptr;
+}
+
+/**
* Used by XULMap.h to map both menupopup and popup elements
*/
LocalAccessible* CreateMenupopupAccessible(Element* aElement,
@@ -1138,13 +1163,19 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
}
}
+ // SVG elements are not in a markup map, but we may still need to create an
+ // accessible for one, even in the case of display:contents.
+ if (!newAcc && content->IsSVGElement()) {
+ newAcc = MaybeCreateSVGAccessible(content, document);
+ }
+
// Check whether this element has an ARIA role or attribute that requires
// us to create an Accessible.
const bool hasNonPresentationalARIARole =
roleMapEntry && !roleMapEntry->Is(nsGkAtoms::presentation) &&
!roleMapEntry->Is(nsGkAtoms::none);
- if (!newAcc && (hasNonPresentationalARIARole ||
- AttributesMustBeAccessible(content, document))) {
+ if (!newAcc &&
+ (hasNonPresentationalARIARole || MustBeAccessible(content, document))) {
newAcc = new HyperTextAccessible(content, document);
}
@@ -1365,32 +1396,7 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
if (!newAcc) {
if (content->IsSVGElement()) {
- if (content->IsSVGGeometryElement() ||
- content->IsSVGElement(nsGkAtoms::image)) {
- // Shape elements: rect, circle, ellipse, line, path, polygon,
- // and polyline. 'use' and 'text' graphic elements require
- // special support.
- if (MustSVGElementBeAccessible(content, document)) {
- newAcc = new EnumRoleAccessible<roles::GRAPHIC>(content, document);
- }
- } else if (content->IsSVGElement(nsGkAtoms::text)) {
- newAcc = new HyperTextAccessible(content->AsElement(), document);
- } else if (content->IsSVGElement(nsGkAtoms::svg)) {
- // An <svg> element could contain <foreignObject>, which contains HTML
- // but does not normally create its own Accessible. This means that the
- // <svg> Accessible could have TextLeafAccessible children, so it must
- // be a HyperTextAccessible.
- newAcc =
- new EnumRoleHyperTextAccessible<roles::DIAGRAM>(content, document);
- } else if (content->IsSVGElement(nsGkAtoms::g) &&
- MustSVGElementBeAccessible(content, document)) {
- // <g> can also contain <foreignObject>.
- newAcc =
- new EnumRoleHyperTextAccessible<roles::GROUPING>(content, document);
- } else if (content->IsSVGElement(nsGkAtoms::a)) {
- newAcc = new HTMLLinkAccessible(content, document);
- }
-
+ newAcc = MaybeCreateSVGAccessible(content, document);
} else if (content->IsMathMLElement()) {
const MarkupMapInfo* markupMap =
mMathMLMarkupMap.Get(content->NodeInfo()->NameAtom());
diff --git a/accessible/base/nsCoreUtils.cpp b/accessible/base/nsCoreUtils.cpp
index c5e89258fa..3e66fa9c23 100644
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -547,32 +547,6 @@ bool nsCoreUtils::IsWhitespaceString(const nsAString& aString) {
return iterBegin == iterEnd;
}
-void nsCoreUtils::TrimNonBreakingSpaces(nsAString& aString) {
- if (aString.IsEmpty()) {
- return;
- }
-
- // Find the index past the last nbsp prefix character.
- constexpr char16_t nbsp{0xA0};
- size_t startIndex = 0;
- while (aString.CharAt(startIndex) == nbsp) {
- startIndex++;
- }
-
- // Find the index before the first nbsp suffix character.
- size_t endIndex = aString.Length() - 1;
- while (endIndex > startIndex && aString.CharAt(endIndex) == nbsp) {
- endIndex--;
- }
- if (startIndex > endIndex) {
- aString.Truncate();
- return;
- }
-
- // Trim the string down, removing the non-breaking space characters.
- aString = Substring(aString, startIndex, endIndex - startIndex + 1);
-}
-
bool nsCoreUtils::AccEventObserversExist() {
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
NS_ENSURE_TRUE(obsService, false);
diff --git a/accessible/base/nsCoreUtils.h b/accessible/base/nsCoreUtils.h
index 5e77d6bfe0..28ead982af 100644
--- a/accessible/base/nsCoreUtils.h
+++ b/accessible/base/nsCoreUtils.h
@@ -307,11 +307,6 @@ class nsCoreUtils {
aChar == 0xa0;
}
- /**
- * Remove non-breaking spaces from the beginning and end of the string.
- */
- static void TrimNonBreakingSpaces(nsAString& aString);
-
/*
* Return true if there are any observers of accessible events.
*/
diff --git a/accessible/basetypes/Accessible.cpp b/accessible/basetypes/Accessible.cpp
index 8b433a0ddd..234c7aa4a1 100644
--- a/accessible/basetypes/Accessible.cpp
+++ b/accessible/basetypes/Accessible.cpp
@@ -508,6 +508,24 @@ const Accessible* Accessible::ActionAncestor() const {
}
nsStaticAtom* Accessible::LandmarkRole() const {
+ // For certain cases below (e.g. ARIA region, HTML <header>), whether it is
+ // actually a landmark is conditional. Rather than duplicating that
+ // conditional logic here, we check the Gecko role.
+ if (const nsRoleMapEntry* roleMapEntry = ARIARoleMap()) {
+ // Explicit ARIA role should take precedence.
+ if (roleMapEntry->Is(nsGkAtoms::region)) {
+ if (Role() == roles::REGION) {
+ return nsGkAtoms::region;
+ }
+ } else if (roleMapEntry->Is(nsGkAtoms::form)) {
+ if (Role() == roles::FORM) {
+ return nsGkAtoms::form;
+ }
+ } else if (roleMapEntry->IsOfType(eLandmark)) {
+ return roleMapEntry->roleAtom;
+ }
+ }
+
nsAtom* tagName = TagName();
if (!tagName) {
// Either no associated content, or no cache.
@@ -539,13 +557,13 @@ nsStaticAtom* Accessible::LandmarkRole() const {
}
if (tagName == nsGkAtoms::section) {
- if (!NameIsEmpty()) {
+ if (Role() == roles::REGION) {
return nsGkAtoms::region;
}
}
if (tagName == nsGkAtoms::form) {
- if (!NameIsEmpty()) {
+ if (Role() == roles::FORM_LANDMARK) {
return nsGkAtoms::form;
}
}
@@ -554,14 +572,14 @@ nsStaticAtom* Accessible::LandmarkRole() const {
return nsGkAtoms::search;
}
- const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
- return roleMapEntry && roleMapEntry->IsOfType(eLandmark)
- ? roleMapEntry->roleAtom
- : nullptr;
+ return nullptr;
}
nsStaticAtom* Accessible::ComputedARIARole() const {
const nsRoleMapEntry* roleMap = ARIARoleMap();
+ if (roleMap && roleMap->IsOfType(eDPub)) {
+ return roleMap->roleAtom;
+ }
if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty &&
// region and form have their own Gecko roles and need to be handled
// specially.
@@ -605,12 +623,15 @@ void Accessible::ApplyImplicitState(uint64_t& aState) const {
}
}
- // If this is an ARIA item of the selectable widget and if it's focused and
- // not marked unselected explicitly (i.e. aria-selected="false") then expose
- // it as selected to make ARIA widget authors life easier.
+ // If this is an option, tab or treeitem and if it's focused and not marked
+ // unselected explicitly (i.e. aria-selected="false") then expose it as
+ // selected to make ARIA widget authors life easier.
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
- if (roleMapEntry && !(aState & states::SELECTED) &&
- ARIASelected().valueOr(true)) {
+ if (roleMapEntry &&
+ (roleMapEntry->Is(nsGkAtoms::option) ||
+ roleMapEntry->Is(nsGkAtoms::tab) ||
+ roleMapEntry->Is(nsGkAtoms::treeitem)) &&
+ !(aState & states::SELECTED) && ARIASelected().valueOr(true)) {
// Special case for tabs: focused tab or focus inside related tab panel
// implies selected state.
if (roleMapEntry->role == roles::PAGETAB) {
diff --git a/accessible/basetypes/Accessible.h b/accessible/basetypes/Accessible.h
index 9068115d01..1848fc6b37 100644
--- a/accessible/basetypes/Accessible.h
+++ b/accessible/basetypes/Accessible.h
@@ -438,10 +438,15 @@ class Accessible {
nsStaticAtom* LandmarkRole() const;
/**
- * Return the id of the dom node this accessible represents.
+ * Return the id of the DOM node this Accessible represents.
*/
virtual void DOMNodeID(nsString& aID) const = 0;
+ /**
+ * Return the class of the DOM node this Accessible represents.
+ */
+ virtual void DOMNodeClass(nsString& aClass) const = 0;
+
//////////////////////////////////////////////////////////////////////////////
// ActionAccessible
diff --git a/accessible/docs/index.rst b/accessible/docs/index.rst
index 881d49ca9b..5626eb1dbc 100644
--- a/accessible/docs/index.rst
+++ b/accessible/docs/index.rst
@@ -6,6 +6,8 @@ They live in the mozilla-central repository under the accessible/docs directory.
The `Accessibility page on the Mozilla Wiki <https://wiki.mozilla.org/Accessibility>`__ contains general information about accessibility and the accessibility team at Mozilla.
+The `Mochitest FAQ docs <../testing/mochitest-plain/faq.html#how-can-i-run-accessibility-tests-a11y-checks>`__ contain guidance on debugging a11y-checks failures and working with AccessibilityUtils when writing frontend test cases.
+
.. toctree::
:maxdepth: 1
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
index aaf0337a1a..b01b758945 100644
--- a/accessible/generic/LocalAccessible.cpp
+++ b/accessible/generic/LocalAccessible.cpp
@@ -14,6 +14,7 @@
#include "mozilla/a11y/AccAttributes.h"
#include "mozilla/a11y/DocAccessibleChild.h"
#include "mozilla/a11y/Platform.h"
+#include "mozilla/FocusModel.h"
#include "nsAccUtils.h"
#include "nsAccessibilityService.h"
#include "ApplicationAccessible.h"
@@ -127,7 +128,6 @@ ENameValueFlag LocalAccessible::Name(nsString& aName) const {
if (!aName.IsEmpty()) return eNameOK;
ENameValueFlag nameFlag = NativeName(aName);
- nsCoreUtils::TrimNonBreakingSpaces(aName);
if (!aName.IsEmpty()) return nameFlag;
// In the end get the name from tooltip.
@@ -408,6 +408,7 @@ uint64_t LocalAccessible::NativeInteractiveState() const {
if (NativelyUnavailable()) return states::UNAVAILABLE;
nsIFrame* frame = GetFrame();
+ auto flags = IsFocusableFlags(0);
// If we're caching this remote document in the parent process, we
// need to cache focusability irrespective of visibility. Otherwise,
// if this document is invisible when it first loads, we'll cache that
@@ -418,13 +419,12 @@ uint64_t LocalAccessible::NativeInteractiveState() const {
// Although ignoring visibility means IsFocusable will return true for
// visibility: hidden, etc., this isn't a problem because we don't include
// those hidden elements in the a11y tree anyway.
- const bool ignoreVisibility = mDoc->IPCDoc();
- if (frame && frame->IsFocusable(
- /* aWithMouse */ false,
- /* aCheckVisibility */ !ignoreVisibility)) {
+ if (mDoc->IPCDoc()) {
+ flags |= IsFocusableFlags::IgnoreVisibility;
+ }
+ if (frame && frame->IsFocusable(flags)) {
return states::FOCUSABLE;
}
-
return 0;
}
@@ -689,7 +689,8 @@ nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
if (frame && mContent) {
*aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
- frame, *aBoundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ frame, *aBoundingFrame,
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);
if (unionRect.IsEmpty()) {
// If we end up with a 0x0 rect from above (or one with negative
@@ -1625,6 +1626,14 @@ void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
*aState |= aria::UniversalStatesFor(element);
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
+ // This is a <td> inside a role="grid", so it gets an implicit role of
+ // GRID_CELL in ARIATransformRole. However, because it's implicit, we
+ // don't have a role map entry, and without that, we can't apply ARIA states
+ // below. Therefore, we get the role map entry here.
+ roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
+ MOZ_ASSERT(roleMapEntry, "Should have role map entry for gridcell");
+ }
if (roleMapEntry) {
// We only force the readonly bit off if we have a real mapping for the aria
// role. This preserves the ability for screen readers to use readonly
@@ -3794,14 +3803,12 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
} else if (aUpdateType == CacheUpdateType::Update) {
fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry());
}
- if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
- nsAutoString className;
- el->GetClassName(className);
- if (!className.IsEmpty()) {
- fields->SetAttribute(CacheKey::DOMNodeClass, std::move(className));
- } else if (aUpdateType == CacheUpdateType::Update) {
- fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
- }
+ nsString className;
+ DOMNodeClass(className);
+ if (!className.IsEmpty()) {
+ fields->SetAttribute(CacheKey::DOMNodeClass, std::move(className));
+ } else if (aUpdateType == CacheUpdateType::Update) {
+ fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
}
}
@@ -4272,6 +4279,13 @@ void LocalAccessible::DOMNodeID(nsString& aID) const {
}
}
+void LocalAccessible::DOMNodeClass(nsString& aClass) const {
+ aClass.Truncate();
+ if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
+ el->GetClassName(aClass);
+ }
+}
+
void LocalAccessible::LiveRegionAttributes(nsAString* aLive,
nsAString* aRelevant,
Maybe<bool>* aAtomic,
diff --git a/accessible/generic/LocalAccessible.h b/accessible/generic/LocalAccessible.h
index 51c4cc9424..eb684b9291 100644
--- a/accessible/generic/LocalAccessible.h
+++ b/accessible/generic/LocalAccessible.h
@@ -736,6 +736,8 @@ class LocalAccessible : public nsISupports, public Accessible {
virtual void DOMNodeID(nsString& aID) const override;
+ virtual void DOMNodeClass(nsString& aClass) const override;
+
virtual void LiveRegionAttributes(nsAString* aLive, nsAString* aRelevant,
Maybe<bool>* aAtomic,
nsAString* aBusy) const override;
diff --git a/accessible/ipc/DocAccessibleChild.h b/accessible/ipc/DocAccessibleChild.h
index 0a6164cce8..01673ba689 100644
--- a/accessible/ipc/DocAccessibleChild.h
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -24,7 +24,9 @@ class AccShowEvent;
*/
class DocAccessibleChild : public PDocAccessibleChild {
public:
- DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager) : mDoc(aDoc) {
+ DocAccessibleChild(DocAccessible* aDoc,
+ mozilla::ipc::IRefCountedProtocol* aManager)
+ : mDoc(aDoc) {
MOZ_COUNT_CTOR(DocAccessibleChild);
SetManager(aManager);
}
diff --git a/accessible/ipc/RemoteAccessible.cpp b/accessible/ipc/RemoteAccessible.cpp
index 772fc58776..0077750ed3 100644
--- a/accessible/ipc/RemoteAccessible.cpp
+++ b/accessible/ipc/RemoteAccessible.cpp
@@ -1329,6 +1329,13 @@ void RemoteAccessible::DOMNodeID(nsString& aID) const {
}
}
+void RemoteAccessible::DOMNodeClass(nsString& aClass) const {
+ if (mCachedFields) {
+ mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass);
+ VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
+ }
+}
+
void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
int32_t aY) {
Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
@@ -1551,7 +1558,7 @@ already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
}
nsString className;
- mCachedFields->GetAttribute(CacheKey::DOMNodeClass, className);
+ DOMNodeClass(className);
if (!className.IsEmpty()) {
attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
}
diff --git a/accessible/ipc/RemoteAccessible.h b/accessible/ipc/RemoteAccessible.h
index 9215fd7bc5..45f41b8fb5 100644
--- a/accessible/ipc/RemoteAccessible.h
+++ b/accessible/ipc/RemoteAccessible.h
@@ -365,6 +365,8 @@ class RemoteAccessible : public Accessible, public HyperTextAccessibleBase {
virtual void DOMNodeID(nsString& aID) const override;
+ virtual void DOMNodeClass(nsString& aClass) const override;
+
virtual void ScrollToPoint(uint32_t aScrollType, int32_t aX,
int32_t aY) override;
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm
index ef2f4ba779..e8107b1690 100644
--- a/accessible/mac/AccessibleWrap.mm
+++ b/accessible/mac/AccessibleWrap.mm
@@ -192,6 +192,9 @@ Class a11y::GetTypeFromRole(roles::Role aRole) {
case roles::RADIO_MENU_ITEM:
return [mozRadioButtonAccessible class];
+ case roles::PROGRESSBAR:
+ return [mozRangeAccessible class];
+
case roles::SPINBUTTON:
case roles::SLIDER:
return [mozIncrementableAccessible class];
diff --git a/accessible/mac/MOXWebAreaAccessible.mm b/accessible/mac/MOXWebAreaAccessible.mm
index c1ae585fa1..37168b1be2 100644
--- a/accessible/mac/MOXWebAreaAccessible.mm
+++ b/accessible/mac/MOXWebAreaAccessible.mm
@@ -211,7 +211,7 @@ using namespace mozilla::a11y;
- (NSArray*)moxUnignoredChildren {
if (id rootGroup = [self rootGroup]) {
- return @[ [self rootGroup] ];
+ return @[ rootGroup ];
}
// There is no root group, expose the children here directly.
diff --git a/accessible/mac/mozActionElements.h b/accessible/mac/mozActionElements.h
index f9940c793a..d5b286ff97 100644
--- a/accessible/mac/mozActionElements.h
+++ b/accessible/mac/mozActionElements.h
@@ -65,15 +65,14 @@
@end
/**
- * Base accessible for an incrementable
+ * Base accessible for a range, an acc with a min, max that cannot
+ * be modified by the user directly.
*/
-@interface mozIncrementableAccessible : mozAccessible
-// override
-- (id)moxValue;
+@interface mozRangeAccessible : mozAccessible
// override
-- (NSString*)moxValueDescription;
+- (id)moxValue;
// override
- (id)moxMinValue;
@@ -82,19 +81,29 @@
- (id)moxMaxValue;
// override
-- (void)moxSetValue:(id)value;
+- (NSString*)moxOrientation;
// override
-- (void)moxPerformIncrement;
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+@end
+
+/**
+ * Base accessible for an incrementable, a settable range
+ */
+@interface mozIncrementableAccessible : mozRangeAccessible
// override
-- (void)moxPerformDecrement;
+- (NSString*)moxValueDescription;
// override
-- (NSString*)moxOrientation;
+- (void)moxSetValue:(id)value;
// override
-- (void)handleAccessibleEvent:(uint32_t)eventType;
+- (void)moxPerformIncrement;
+
+// override
+- (void)moxPerformDecrement;
- (void)changeValueBySteps:(int)factor;
diff --git a/accessible/mac/mozActionElements.mm b/accessible/mac/mozActionElements.mm
index f39f2c8ad5..e3a2ff9598 100644
--- a/accessible/mac/mozActionElements.mm
+++ b/accessible/mac/mozActionElements.mm
@@ -134,17 +134,12 @@ using namespace mozilla::a11y;
@end
-@implementation mozIncrementableAccessible
+@implementation mozRangeAccessible
- (id)moxValue {
return [NSNumber numberWithDouble:mGeckoAccessible->CurValue()];
}
-- (NSString*)moxValueDescription {
- nsAutoString valueDesc;
- mGeckoAccessible->Value(valueDesc);
- return nsCocoaUtils::ToNSString(valueDesc);
-}
- (id)moxMinValue {
return [NSNumber numberWithDouble:mGeckoAccessible->MinValue()];
}
@@ -153,18 +148,6 @@ using namespace mozilla::a11y;
return [NSNumber numberWithDouble:mGeckoAccessible->MaxValue()];
}
-- (void)moxSetValue:(id)value {
- [self setValue:([value doubleValue])];
-}
-
-- (void)moxPerformIncrement {
- [self changeValueBySteps:1];
-}
-
-- (void)moxPerformDecrement {
- [self changeValueBySteps:-1];
-}
-
- (NSString*)moxOrientation {
RefPtr<AccAttributes> attributes = mGeckoAccessible->Attributes();
if (attributes) {
@@ -192,6 +175,28 @@ using namespace mozilla::a11y;
}
}
+@end
+
+@implementation mozIncrementableAccessible
+
+- (NSString*)moxValueDescription {
+ nsAutoString valueDesc;
+ mGeckoAccessible->Value(valueDesc);
+ return nsCocoaUtils::ToNSString(valueDesc);
+}
+
+- (void)moxSetValue:(id)value {
+ [self setValue:([value doubleValue])];
+}
+
+- (void)moxPerformIncrement {
+ [self changeValueBySteps:1];
+}
+
+- (void)moxPerformDecrement {
+ [self changeValueBySteps:-1];
+}
+
/*
* Updates the accessible's current value by factor and step.
*
diff --git a/accessible/tests/browser/e10s/browser.toml b/accessible/tests/browser/e10s/browser.toml
index dff9b1c712..a4205d38e6 100644
--- a/accessible/tests/browser/e10s/browser.toml
+++ b/accessible/tests/browser/e10s/browser.toml
@@ -31,6 +31,7 @@ prefs = [
["browser_caching_description.js"]
["browser_caching_document_props.js"]
+https_first_disabled = true
["browser_caching_domnodeid.js"]
diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js
index 7292228f25..6f674f8c48 100644
--- a/accessible/tests/browser/e10s/browser_caching_states.js
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -425,6 +425,11 @@ addAccessibleTask(
<div role="listbox" aria-multiselectable="true">
<div id="multiNoSel" role="option" tabindex="0">multiNoSel</div>
</div>
+<div role="grid">
+ <div role="row">
+ <div id="gridcell" role="gridcell" tabindex="0">gridcell</div>
+ </div>
+</div>
`,
async function (browser, docAcc) {
const noSel = findAccessibleChildByID(docAcc, "noSel");
@@ -450,6 +455,14 @@ addAccessibleTask(
multiNoSel.takeFocus();
await focused;
testStates(multiNoSel, STATE_FOCUSED, 0, STATE_SELECTED, 0);
+
+ const gridcell = findAccessibleChildByID(docAcc, "gridcell");
+ testStates(gridcell, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing gridcell");
+ focused = waitForEvent(EVENT_FOCUS, gridcell);
+ gridcell.takeFocus();
+ await focused;
+ testStates(gridcell, STATE_FOCUSED, 0, STATE_SELECTED, 0);
},
{ topLevel: true, iframe: true, remoteIframe: true, chrome: true }
);
diff --git a/accessible/tests/browser/e10s/head.js b/accessible/tests/browser/e10s/head.js
index bdbcb7445f..e72af914d4 100644
--- a/accessible/tests/browser/e10s/head.js
+++ b/accessible/tests/browser/e10s/head.js
@@ -170,6 +170,11 @@ async function testRelated(
expected: [null, host, dependant2],
},
{
+ desc: "Change attribute to multiple targets",
+ attrs: [{ key: attr, value: "dependant1 dependant2" }],
+ expected: [host, host, [dependant1, dependant2]],
+ },
+ {
desc: "Remove attribute",
attrs: [{ key: attr }],
expected: [null, null, null],
diff --git a/accessible/tests/browser/mac/browser_range.js b/accessible/tests/browser/mac/browser_range.js
index 430e41d6ea..8a5bafba50 100644
--- a/accessible/tests/browser/mac/browser_range.js
+++ b/accessible/tests/browser/mac/browser_range.js
@@ -188,3 +188,57 @@ addAccessibleTask(
is(slider.getAttributeValue("AXMaxValue"), 5, "Correct max value");
}
);
+
+/**
+ * Verify progress HTML elements expose their min, max, and value to VO.
+ * Progress elements should not expose a value description, and should not
+ * expose increment/decrement actions.
+ */
+addAccessibleTask(
+ `<progress id="progress" value="70" max="100"></progress>`,
+ async (browser, accDoc) => {
+ const progress = getNativeInterface(accDoc, "progress");
+ is(progress.getAttributeValue("AXValue"), 70, "Correct value");
+ is(progress.getAttributeValue("AXMaxValue"), 100, "Correct max value");
+ is(progress.getAttributeValue("AXMinValue"), 0, "Correct min value");
+ is(
+ progress.getAttributeValue("AXValueDescription"),
+ null,
+ "Progress elements should not expose a value description"
+ );
+ for (let action of progress.actionNames) {
+ isnot(
+ action,
+ "AXIncrement",
+ "Progress elements should not expose increment action"
+ );
+ isnot(
+ action,
+ "AXDecrement",
+ "Progress elements should not expose decrement action"
+ );
+ }
+ }
+);
+
+/**
+ * Verify progress HTML elements expose changes to their value.
+ */
+addAccessibleTask(
+ `<progress id="progress" value="70" max="100"></progress>`,
+ async (browser, accDoc) => {
+ const progress = getNativeInterface(accDoc, "progress");
+ is(progress.getAttributeValue("AXValue"), 70, "Correct value");
+ is(progress.getAttributeValue("AXMaxValue"), 100, "Correct max value");
+ is(progress.getAttributeValue("AXMinValue"), 0, "Correct min value");
+
+ const evt = waitForMacEvent("AXValueChanged");
+ await invokeContentTask(browser, [], () => {
+ const p = content.document.getElementById("progress");
+ p.setAttribute("value", "90");
+ });
+ await evt;
+
+ is(progress.getAttributeValue("AXValue"), 90, "Correct updated value");
+ }
+);
diff --git a/accessible/tests/browser/python_runner_wsh.py b/accessible/tests/browser/python_runner_wsh.py
index 488051240f..2686967cfb 100644
--- a/accessible/tests/browser/python_runner_wsh.py
+++ b/accessible/tests/browser/python_runner_wsh.py
@@ -9,6 +9,7 @@ It is intended to be called from JS browser tests.
"""
import json
+import math
import os
import sys
import traceback
@@ -83,6 +84,9 @@ def web_socket_transfer_data(request):
exec(code, namespace)
# Run the function we just defined.
ret = namespace["run"]()
+ if isinstance(ret, float) and math.isnan(ret):
+ # NaN can't be serialized by JSON.
+ ret = None
send("return", ret)
except Exception:
send("exception", traceback.format_exc())
diff --git a/accessible/tests/browser/selectable/browser_test_aria_select.js b/accessible/tests/browser/selectable/browser_test_aria_select.js
index dbc36956f8..2c3a80586e 100644
--- a/accessible/tests/browser/selectable/browser_test_aria_select.js
+++ b/accessible/tests/browser/selectable/browser_test_aria_select.js
@@ -117,7 +117,8 @@ addAccessibleTask(
// ////////////////////////////////////////////////////////////////////////
// role="grid" aria-multiselectable, selectable children in subtree
addAccessibleTask(
- `<table tabindex="0" border="2" cellspacing="0" id="grid" role="grid"
+ `
+ <table tabindex="0" border="2" cellspacing="0" id="grid" role="grid"
aria-multiselectable="true">
<thead>
<tr>
@@ -133,19 +134,29 @@ addAccessibleTask(
<tr>
<td tabindex="-1" role="rowheader" id="grid_rowhead"
aria-readonly="true">1</td>
- <td tabindex="-1" role="gridcell" id="grid_cell1"
+ <td tabindex="-1" id="grid_cell1"
aria-selected="true">03/14/05</td>
- <td tabindex="-1" role="gridcell" id="grid_cell2"
+ <td tabindex="-1" id="grid_cell2"
aria-selected="false">Conference Fee</td>
</tr>
- </tobdy>
- </table>`,
+ </tbody>
+ </table>
+ <table id="table">
+ <tr><th>a</th><td id="tableB" aria-selected="true">b</td></tr>
+ </table>
+ `,
async function (browser, docAcc) {
info('role="grid" aria-multiselectable, selectable children in subtree');
- let grid = findAccessibleChildByID(docAcc, "grid", [
+ const grid = findAccessibleChildByID(docAcc, "grid", [
nsIAccessibleSelectable,
]);
+ // grid_cell1 is a <td> with an implicit role of gridcell.
testSelectableSelection(grid, ["grid_colhead1", "grid_cell1"]);
+ info("Verify aria-selected doesn't apply to <td> that isn't gridcell");
+ // We can't use testSelectableSelection here because table (rightly) isn't a
+ // selectable container.
+ const tableB = findAccessibleChildByID(docAcc, "tableB");
+ testStates(tableB, 0, 0, STATE_SELECTED, 0);
},
{
chrome: true,
diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml
index 75728f56d7..13b1c12cc0 100644
--- a/accessible/tests/browser/windows/uia/browser.toml
+++ b/accessible/tests/browser/windows/uia/browser.toml
@@ -11,8 +11,15 @@ support-files = ["head.js"]
["browser_elementFromPoint.js"]
["browser_focus.js"]
+
["browser_generalProps.js"]
+["browser_gridPatterns.js"]
+
+["browser_relationProps.js"]
+
+["browser_selectionPatterns.js"]
+
["browser_simplePatterns.js"]
["browser_tree.js"]
diff --git a/accessible/tests/browser/windows/uia/browser_generalProps.js b/accessible/tests/browser/windows/uia/browser_generalProps.js
index 5cfda226d0..244c9e4b1b 100644
--- a/accessible/tests/browser/windows/uia/browser_generalProps.js
+++ b/accessible/tests/browser/windows/uia/browser_generalProps.js
@@ -4,6 +4,12 @@
"use strict";
+/* eslint-disable camelcase */
+// From https://learn.microsoft.com/en-us/windows/win32/winauto/landmark-type-identifiers
+const UIA_CustomLandmarkTypeId = 80000;
+const UIA_MainLandmarkTypeId = 80002;
+/* eslint-enable camelcase */
+
/**
* Test the Name property.
*/
@@ -103,3 +109,347 @@ addUiaTask(
ok(await runPython(`p.CurrentIsEnabled`), "p has IsEnabled true");
}
);
+
+async function testGroupPos(id, level, pos, size) {
+ await assignPyVarToUiaWithId(id);
+ is(await runPython(`${id}.CurrentLevel`), level, `${id} Level correct`);
+ is(
+ await runPython(`${id}.CurrentPositionInSet`),
+ pos,
+ `${id} PositionInSet correct`
+ );
+ is(
+ await runPython(`${id}.CurrentSizeOfSet`),
+ size,
+ `${id} SizeOfSet correct`
+ );
+}
+
+/**
+ * Test the Level, PositionInSet and SizeOfSet properties.
+ */
+addUiaTask(
+ `
+<ul>
+ <li id="li1">li1<ul id="ul1">
+ <li id="li2a">li2a</li>
+ <li id="li2b" hidden>li2b</li>
+ <li id="li2c">li2c</li>
+ </ul></li>
+</ul>
+<h2 id="h2">h2</h2>
+<button id="button">button</button>
+ `,
+ async function testGroupPosProps(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testGroupPos("li1", 1, 1, 1);
+ await testGroupPos("li2a", 2, 1, 2);
+ await testGroupPos("li2c", 2, 2, 2);
+ info("Showing li2b");
+ // There aren't events in any API for a change to group position properties
+ // because this would be too spammy and isn't particularly useful given
+ // how frequently these can change.
+ let shown = waitForEvent(EVENT_SHOW, "li2b");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("li2b").hidden = false;
+ });
+ await shown;
+ await testGroupPos("li2a", 2, 1, 3);
+ await testGroupPos("li2b", 2, 2, 3);
+ await testGroupPos("li2c", 2, 3, 3);
+
+ // The IA2 -> UIA proxy doesn't map heading level to the Level property.
+ if (gIsUiaEnabled) {
+ await testGroupPos("h2", 2, 0, 0);
+ }
+ await testGroupPos("button", 0, 0, 0);
+ }
+);
+
+/**
+ * Test the FrameworkId property.
+ */
+addUiaTask(
+ `<button id="button">button</button>`,
+ async function testFrameworkId() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`doc.CurrentFrameworkId`),
+ "Gecko",
+ "doc FrameworkId is correct"
+ );
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentFrameworkId`),
+ "Gecko",
+ "button FrameworkId is correct"
+ );
+ }
+);
+
+/**
+ * Test the ClassName property.
+ */
+addUiaTask(
+ `
+<p id="p">p</p>
+<button id="button" class="c1">button</button>
+ `,
+ async function testClassName(browser, docAcc) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("p");
+ ok(!(await runPython(`p.CurrentClassName`)), "p has no ClassName");
+
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentClassName`),
+ "c1",
+ "button has correct ClassName"
+ );
+ info("Changing button class");
+ await invokeSetAttribute(browser, "button", "class", "c2 c3");
+ // Gecko doesn't fire an event for class changes, as this isn't useful for
+ // clients.
+ const button = findAccessibleChildByID(docAcc, "button");
+ await untilCacheIs(
+ () => button.attributes.getStringProperty("class"),
+ "c2 c3",
+ "button class updated"
+ );
+ is(
+ await runPython(`button.CurrentClassName`),
+ "c2 c3",
+ "button has correct ClassName"
+ );
+ },
+ // The IA2 -> UIA proxy doesn't support ClassName.
+ { uiaEnabled: true, uiaDisabled: false }
+);
+
+/**
+ * Test the AriaRole property.
+ */
+addUiaTask(
+ `
+<div id="button" role="button">button</div>
+<div id="main" role="main">main</div>
+<div id="unknown" role="unknown">unknown</div>
+<button id="computedButton">computedButton</button>
+<h1 id="computedHeading">computedHeading</h1>
+<main id="computedMain">computedMain</main>
+<div id="generic">generic</div>
+ `,
+ async function testAriaRole() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`findUiaByDomId(doc, "button").CurrentAriaRole`),
+ "button",
+ "button has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "main").CurrentAriaRole`),
+ "main",
+ "main has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "unknown").CurrentAriaRole`),
+ "unknown",
+ "unknown has correct AriaRole"
+ );
+ // The IA2 -> UIA proxy doesn't compute ARIA roles.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "computedButton").CurrentAriaRole`
+ ),
+ "button",
+ "computedButton has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "computedMain").CurrentAriaRole`),
+ "main",
+ "computedMain has correct AriaRole"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "computedHeading").CurrentAriaRole`
+ ),
+ "heading",
+ "computedHeading has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "generic").CurrentAriaRole`),
+ "generic",
+ "generic has correct AriaRole"
+ );
+ }
+ }
+);
+
+/**
+ * Test the LocalizedControlType property. We don't support this ourselves, but
+ * the system provides it based on ControlType and AriaRole.
+ */
+addUiaTask(
+ `
+<button id="button">button</button>
+<h1 id="h1">h1</h1>
+<main id="main">main</main>
+ `,
+ async function testLocalizedControlType() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "button").CurrentLocalizedControlType`
+ ),
+ "button",
+ "button has correct LocalizedControlType"
+ );
+ // The IA2 -> UIA proxy doesn't compute ARIA roles, so it can't compute the
+ // correct LocalizedControlType for these either.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "h1").CurrentLocalizedControlType`
+ ),
+ "heading",
+ "h1 has correct LocalizedControlType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "main").CurrentLocalizedControlType`
+ ),
+ "main",
+ "main has correct LocalizedControlType"
+ );
+ }
+ }
+);
+
+/**
+ * Test the LandmarkType property.
+ */
+addUiaTask(
+ `
+<div id="main" role="main">main</div>
+<main id="htmlMain">htmlMain</main>
+<div id="banner" role="banner">banner</div>
+<header id="header">header</header>
+<div id="region" role="region" aria-label="region">region</div>
+<div id="unnamedRegion" role="region">unnamedRegion</div>
+<main id="mainBanner" role="banner">mainBanner</main>
+<div id="none">none</div>
+ `,
+ async function testLandmarkType() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`findUiaByDomId(doc, "main").CurrentLandmarkType`),
+ UIA_MainLandmarkTypeId,
+ "main has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "htmlMain").CurrentLandmarkType`),
+ UIA_MainLandmarkTypeId,
+ "htmlMain has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "banner").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "banner has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "header").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "header has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "region").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "region has correct LandmarkType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "unnamedRegion").CurrentLandmarkType`
+ ),
+ 0,
+ "unnamedRegion has correct LandmarkType"
+ );
+ // ARIA role takes precedence.
+ is(
+ await runPython(`findUiaByDomId(doc, "mainBanner").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "mainBanner has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "none").CurrentLandmarkType`),
+ 0,
+ "none has correct LandmarkType"
+ );
+ }
+);
+
+/**
+ * Test the LocalizedLandmarkType property.
+ */
+addUiaTask(
+ `
+<div id="main" role="main">main</div>
+<div id="contentinfo" role="contentinfo">contentinfo</div>
+<div id="region" role="region" aria-label="region">region</div>
+<div id="unnamedRegion" role="region">unnamedRegion</div>
+<main id="mainBanner" role="banner">mainBanner</main>
+<div id="none">none</div>
+ `,
+ async function testLocalizedLandmarkType() {
+ await definePyVar("doc", `getDocUia()`);
+ // Provided by the system.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "main").CurrentLocalizedLandmarkType`
+ ),
+ "main",
+ "main has correct LocalizedLandmarkType"
+ );
+ // The IA2 -> UIA proxy doesn't follow the Core AAM spec for this role.
+ if (gIsUiaEnabled) {
+ // Provided by us.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "contentinfo").CurrentLocalizedLandmarkType`
+ ),
+ "content information",
+ "contentinfo has correct LocalizedLandmarkType"
+ );
+ }
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "region").CurrentLocalizedLandmarkType`
+ ),
+ "region",
+ "region has correct LocalizedLandmarkType"
+ );
+ // Invalid landmark.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "unnamedRegion").CurrentLocalizedLandmarkType`
+ ),
+ "",
+ "unnamedRegion has correct LocalizedLandmarkType"
+ );
+ // ARIA role takes precedence.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "mainBanner").CurrentLocalizedLandmarkType`
+ ),
+ "banner",
+ "mainBanner has correct LocalizedLandmarkType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "none").CurrentLocalizedLandmarkType`
+ ),
+ "",
+ "none has correct LocalizedLandmarkType"
+ );
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_gridPatterns.js b/accessible/tests/browser/windows/uia/browser_gridPatterns.js
new file mode 100644
index 0000000000..24c80a6340
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_gridPatterns.js
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* eslint-disable camelcase */
+const RowOrColumnMajor_RowMajor = 0;
+/* eslint-enable camelcase */
+
+const SNIPPET = `
+<table id="table">
+ <tr><th id="a">a</th><th id="b">b</th><th id="c">c</th></tr>
+ <tr><th id="dg" rowspan="2">dg</th><td id="ef" colspan="2" headers="b c">ef</td></tr>
+ <tr><th id="h">h</th><td id="i" headers="dg h">i</td></tr>
+ <tr><td id="jkl" colspan="3" headers="a b c">jkl</td></tr>
+</table>
+<button id="button">button</button>
+`;
+
+async function testGridGetItem(row, col, cellId) {
+ is(
+ await runPython(`pattern.GetItem(${row}, ${col}).CurrentAutomationId`),
+ cellId,
+ `GetItem with row ${row} and col ${col} returned ${cellId}`
+ );
+}
+
+async function testGridItemProps(id, row, col, rowSpan, colSpan, gridId) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "GridItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has GridItem pattern`);
+ is(await runPython(`pattern.CurrentRow`), row, `${id} has correct Row`);
+ is(await runPython(`pattern.CurrentColumn`), col, `${id} has correct Column`);
+ is(
+ await runPython(`pattern.CurrentRowSpan`),
+ rowSpan,
+ `${id} has correct RowSpan`
+ );
+ is(
+ await runPython(`pattern.CurrentColumnSpan`),
+ colSpan,
+ `${id} has correct ColumnSpan`
+ );
+ is(
+ await runPython(`pattern.CurrentContainingGrid.CurrentAutomationId`),
+ gridId,
+ `${id} ContainingGridItem is ${gridId}`
+ );
+}
+
+async function testTableItemProps(id, rowHeaders, colHeaders) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "TableItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has TableItem pattern`);
+ await isUiaElementArray(
+ `pattern.GetCurrentRowHeaderItems()`,
+ rowHeaders,
+ `${id} has correct RowHeaderItems`
+ );
+ await isUiaElementArray(
+ `pattern.GetCurrentColumnHeaderItems()`,
+ colHeaders,
+ `${id} has correct ColumnHeaderItems`
+ );
+}
+
+/**
+ * Test the Grid pattern.
+ */
+addUiaTask(SNIPPET, async function testGrid() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("table");
+ await definePyVar("pattern", `getUiaPattern(table, "Grid")`);
+ ok(await runPython(`bool(pattern)`), "table has Grid pattern");
+ is(
+ await runPython(`pattern.CurrentRowCount`),
+ 4,
+ "table has correct RowCount"
+ );
+ is(
+ await runPython(`pattern.CurrentColumnCount`),
+ 3,
+ "table has correct ColumnCount"
+ );
+ await testGridGetItem(0, 0, "a");
+ await testGridGetItem(0, 1, "b");
+ await testGridGetItem(0, 2, "c");
+ await testGridGetItem(1, 0, "dg");
+ await testGridGetItem(1, 1, "ef");
+ await testGridGetItem(1, 2, "ef");
+ await testGridGetItem(2, 0, "dg");
+ await testGridGetItem(2, 1, "h");
+ await testGridGetItem(2, 2, "i");
+
+ await testPatternAbsent("button", "Grid");
+});
+
+/**
+ * Test the GridItem pattern.
+ */
+addUiaTask(SNIPPET, async function testGridItem() {
+ await definePyVar("doc", `getDocUia()`);
+ await testGridItemProps("a", 0, 0, 1, 1, "table");
+ await testGridItemProps("b", 0, 1, 1, 1, "table");
+ await testGridItemProps("c", 0, 2, 1, 1, "table");
+ await testGridItemProps("dg", 1, 0, 2, 1, "table");
+ await testGridItemProps("ef", 1, 1, 1, 2, "table");
+ await testGridItemProps("jkl", 3, 0, 1, 3, "table");
+
+ await testPatternAbsent("button", "GridItem");
+});
+
+/**
+ * Test the Table pattern.
+ */
+addUiaTask(
+ SNIPPET,
+ async function testTable() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("table");
+ await definePyVar("pattern", `getUiaPattern(table, "Table")`);
+ ok(await runPython(`bool(pattern)`), "table has Table pattern");
+ await isUiaElementArray(
+ `pattern.GetCurrentRowHeaders()`,
+ ["dg", "h"],
+ "table has correct RowHeaders"
+ );
+ await isUiaElementArray(
+ `pattern.GetCurrentColumnHeaders()`,
+ ["a", "b", "c"],
+ "table has correct ColumnHeaders"
+ );
+ is(
+ await runPython(`pattern.CurrentRowOrColumnMajor`),
+ RowOrColumnMajor_RowMajor,
+ "table has correct RowOrColumnMajor"
+ );
+
+ await testPatternAbsent("button", "Table");
+ },
+ // The IA2 -> UIA proxy doesn't support the Row/ColumnHeaders properties.
+ { uiaEnabled: true, uiaDisabled: false }
+);
+
+/**
+ * Test the TableItem pattern.
+ */
+addUiaTask(SNIPPET, async function testTableItem() {
+ await definePyVar("doc", `getDocUia()`);
+ await testTableItemProps("a", [], []);
+ await testTableItemProps("b", [], []);
+ await testTableItemProps("c", [], []);
+ await testTableItemProps("dg", [], ["a"]);
+ await testTableItemProps("ef", ["dg"], ["b", "c"]);
+ await testTableItemProps("h", ["dg"], ["b"]);
+ await testTableItemProps("i", ["dg", "h"], ["c"]);
+ await testTableItemProps("jkl", [], ["a", "b", "c"]);
+
+ await testPatternAbsent("button", "TableItem");
+});
diff --git a/accessible/tests/browser/windows/uia/browser_relationProps.js b/accessible/tests/browser/windows/uia/browser_relationProps.js
new file mode 100644
index 0000000000..ff4059f99e
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_relationProps.js
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function testUiaRelationArray(id, prop, targets) {
+ return isUiaElementArray(
+ `findUiaByDomId(doc, "${id}").Current${prop}`,
+ targets,
+ `${id} has correct ${prop} targets`
+ );
+}
+
+/**
+ * Test the ControllerFor property.
+ */
+addUiaTask(
+ `
+<input id="controls" aria-controls="t1 t2">
+<input id="error" aria-errormessage="t3 t4" aria-invalid="true">
+<input id="controlsError" aria-controls="t1 t2" aria-errormessage="t3 t4" aria-invalid="true">
+<div id="t1">t1</div>
+<div id="t2">t2</div>
+<div id="t3">t3</div>
+<div id="t4">t4</div>
+<button id="none">none</button>
+ `,
+ async function testControllerFor() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("controls", "ControllerFor", ["t1", "t2"]);
+ // The IA2 -> UIA proxy doesn't support IA2_RELATION_ERROR.
+ if (gIsUiaEnabled) {
+ await testUiaRelationArray("error", "ControllerFor", ["t3", "t4"]);
+ await testUiaRelationArray("controlsError", "ControllerFor", [
+ "t1",
+ "t2",
+ "t3",
+ "t4",
+ ]);
+ }
+ await testUiaRelationArray("none", "ControllerFor", []);
+ }
+);
+
+/**
+ * Test the DescribedBy property.
+ */
+addUiaTask(
+ `
+<input id="describedby" aria-describedby="t1 t2">
+<input id="details" aria-details="t3 t4">
+<input id="describedbyDetails" aria-describedby="t1 t2" aria-details="t3 t4" aria-invalid="true">
+<div id="t1">t1</div>
+<div id="t2">t2</div>
+<div id="t3">t3</div>
+<div id="t4">t4</div>
+<button id="none">none</button>
+ `,
+ async function testDescribedBy() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("describedby", "DescribedBy", ["t1", "t2"]);
+ // The IA2 -> UIA proxy doesn't support IA2_RELATION_DETAILS.
+ if (gIsUiaEnabled) {
+ await testUiaRelationArray("details", "DescribedBy", ["t3", "t4"]);
+ await testUiaRelationArray("describedbyDetails", "DescribedBy", [
+ "t1",
+ "t2",
+ "t3",
+ "t4",
+ ]);
+ }
+ await testUiaRelationArray("none", "DescribedBy", []);
+ }
+);
+
+/**
+ * Test the FlowsFrom and FlowsTo properties.
+ */
+addUiaTask(
+ `
+<div id="t1" aria-flowto="t2">t1</div>
+<div id="t2">t2</div>
+<button id="none">none</button>
+ `,
+ async function testFlows() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("t1", "FlowsTo", ["t2"]);
+ await testUiaRelationArray("t2", "FlowsFrom", ["t1"]);
+ await testUiaRelationArray("none", "FlowsFrom", []);
+ await testUiaRelationArray("none", "FlowsTo", []);
+ }
+);
+
+/**
+ * Test the LabeledBy property.
+ */
+addUiaTask(
+ `
+<label id="label">label</label>
+<input id="input" aria-labelledby="label">
+<label id="wrappingLabel">
+ <input id="wrappedInput" value="wrappedInput">
+ <p id="wrappingLabelP">wrappingLabel</p>
+</label>
+<button id="button" aria-labelledby="label">content</button>
+<button id="noLabel">noLabel</button>
+ `,
+ async function testLabeledBy() {
+ await definePyVar("doc", `getDocUia()`);
+ // input's LabeledBy should be label's text leaf.
+ let result = await runPython(`
+ input = findUiaByDomId(doc, "input")
+ label = findUiaByDomId(doc, "label")
+ labelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(label)
+ return uiaClient.CompareElements(input.CurrentLabeledBy, labelLeaf)
+ `);
+ ok(result, "input has correct LabeledBy");
+ // wrappedInput's LabeledBy should be wrappingLabelP's text leaf.
+ result = await runPython(`
+ wrappedInput = findUiaByDomId(doc, "wrappedInput")
+ wrappingLabelP = findUiaByDomId(doc, "wrappingLabelP")
+ wrappingLabelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(wrappingLabelP)
+ return uiaClient.CompareElements(wrappedInput.CurrentLabeledBy, wrappingLabelLeaf)
+ `);
+ ok(result, "wrappedInput has correct LabeledBy");
+ // button has aria-labelledby, but UIA prohibits LabeledBy on buttons.
+ ok(
+ !(await runPython(
+ `bool(findUiaByDomId(doc, "button").CurrentLabeledBy)`
+ )),
+ "button has no LabeledBy"
+ );
+ ok(
+ !(await runPython(
+ `bool(findUiaByDomId(doc, "noLabel").CurrentLabeledBy)`
+ )),
+ "noLabel has no LabeledBy"
+ );
+ },
+ // The IA2 -> UIA proxy doesn't expose LabeledBy properly.
+ { uiaEnabled: true, uiaDisabled: false }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_selectionPatterns.js b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js
new file mode 100644
index 0000000000..a1f70b886a
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const SNIPPET = `
+<select id="selectList" size="2">
+ <option id="sl1" selected>sl1</option>
+ <option id="sl2">sl2</option>
+</select>
+<select id="selectRequired" size="2" required>
+ <option id="sr1">sr1</option>
+</select>
+<select id="selectMulti" size="2" multiple>
+ <option id="sm1" selected>sm1</option>
+ <option id="sm2">sm2</option>
+ <option id="sm3" selected>sm3</option>
+ <option id="sm4">sm4</option>
+ <option id="sm5">sm5</option>
+ <option id="sm6">sm6</option>
+</select>
+<select id="selectCombo" size="1">
+ <option>sc1</option>
+</select>
+<div id="ariaListbox" role="listbox">
+ <div id="al1" role="option" aria-selected="true">al1</div>
+ <div id="al2" role="option">al2</div>
+</div>
+<div id="tablist" role="tablist">
+ <div id="t1" role="tab">t1</div>
+ <div id="t2" role="tab" aria-selected="true">t2</div>
+</div>
+<table id="grid" role="grid" aria-multiselectable="true">
+ <tr>
+ <td id="g1">g1</td>
+ <td id="g2" role="gridcell" aria-selected="true">g2</td>
+ </tr>
+ <tr>
+ <td id="g3">g3</td>
+ <td id="g4" role="gridcell" aria-selected="true">g4</td>
+ </tr>
+</table>
+<div id="radiogroup" role="radiogroup">
+ <label><input id="r1" type="radio" name="r" checked>r1</label>
+ <label><input id="r2" type="radio" name="r">r2</label>
+</div>
+<div id="menu" role="menu">
+ <div id="m1" role="menuitem">m1</div>
+ <div id="m2" role="menuitemradio">m2</div>
+ <div id="m3" role="menuitemradio" aria-checked="true">m3</div>
+</div>
+<button id="button">button</button>
+`;
+
+async function testSelectionProps(id, selection, multiple, required) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "Selection")`);
+ ok(await runPython(`bool(pattern)`), `${id} has Selection pattern`);
+ await isUiaElementArray(
+ `pattern.GetCurrentSelection()`,
+ selection,
+ `${id} has correct Selection`
+ );
+ is(
+ !!(await runPython(`pattern.CurrentCanSelectMultiple`)),
+ multiple,
+ `${id} has correct CanSelectMultiple`
+ );
+ // The IA2 -> UIA proxy doesn't reflect the required state correctly.
+ if (gIsUiaEnabled) {
+ is(
+ !!(await runPython(`pattern.CurrentIsSelectionRequired`)),
+ required,
+ `${id} has correct IsSelectionRequired`
+ );
+ }
+}
+
+async function testSelectionItemProps(id, selected, container) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "SelectionItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has SelectionItem pattern`);
+ is(
+ !!(await runPython(`pattern.CurrentIsSelected`)),
+ selected,
+ `${id} has correct IsSelected`
+ );
+ if (container) {
+ is(
+ await runPython(`pattern.CurrentSelectionContainer.CurrentAutomationId`),
+ container,
+ `${id} has correct SelectionContainer`
+ );
+ } else {
+ ok(
+ !(await runPython(`bool(pattern.CurrentSelectionContainer)`)),
+ `${id} has no SelectionContainer`
+ );
+ }
+}
+
+/**
+ * Test the Selection pattern.
+ */
+addUiaTask(SNIPPET, async function testSelection(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testSelectionProps("selectList", ["sl1"], false, false);
+ await testSelectionProps("selectRequired", [], false, true);
+
+ await testSelectionProps("selectMulti", ["sm1", "sm3"], true, false);
+ // The Selection pattern only has an event for a complete invalidation of the
+ // container's selection, which only happens when there are too many selection
+ // events. Smaller selection changes fire events in the SelectionItem pattern.
+ info("Changing selectMulti selection");
+ await setUpWaitForUiaEvent("Selection_Invalidated", "selectMulti");
+ await invokeContentTask(browser, [], () => {
+ const multi = content.document.getElementById("selectMulti");
+ multi[0].selected = false;
+ multi[1].selected = true;
+ multi[2].selected = false;
+ multi[3].selected = true;
+ multi[4].selected = true;
+ multi[5].selected = true;
+ });
+ await waitForUiaEvent();
+ ok(true, "select got Invalidated event");
+ await testSelectionProps(
+ "selectMulti",
+ ["sm2", "sm4", "sm5", "sm6"],
+ true,
+ false
+ );
+
+ await testPatternAbsent("selectCombo", "Selection");
+
+ await testSelectionProps("ariaListbox", ["al1"], false, false);
+ await testSelectionProps("tablist", ["t2"], false, false);
+ // The IA2 -> UIA proxy doesn't expose the Selection pattern on grids.
+ if (gIsUiaEnabled) {
+ await testSelectionProps("grid", ["g2", "g4"], true, false);
+ }
+
+ // radio gets the SelectionItem pattern, but radiogroup doesn't get the
+ // Selection pattern for now. Same for menu/menuitemradio.
+ await testPatternAbsent("radiogroup", "Selection");
+ await testPatternAbsent("menu", "Selection");
+
+ await testPatternAbsent("button", "Selection");
+});
+
+/**
+ * Test the SelectionItem pattern.
+ */
+addUiaTask(SNIPPET, async function testSelection() {
+ await definePyVar("doc", `getDocUia()`);
+ await testPatternAbsent("selectList", "SelectionItem");
+ await testSelectionItemProps("sl1", true, "selectList");
+ await testSelectionItemProps("sl2", false, "selectList");
+ info("Calling Select on sl2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "sl2");
+ await runPython(`pattern.Select()`);
+ await waitForUiaEvent();
+ ok(true, "sl2 got ElementSelected event");
+ await testSelectionItemProps("sl1", false, "selectList");
+ await testSelectionItemProps("sl2", true, "selectList");
+
+ await testSelectionItemProps("sr1", false, "selectRequired");
+
+ await testSelectionItemProps("sm1", true, "selectMulti");
+ await testSelectionItemProps("sm2", false, "selectMulti");
+ info("Calling AddToSelection on sm2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementAddedToSelection", "sm2");
+ await runPython(`pattern.AddToSelection()`);
+ await waitForUiaEvent();
+ ok(true, "sm2 got ElementAddedToSelection event");
+ await testSelectionItemProps("sm2", true, "selectMulti");
+ await testSelectionItemProps("sm3", true, "selectMulti");
+ info("Calling RemoveFromSelection on sm3");
+ await setUpWaitForUiaEvent(
+ "SelectionItem_ElementRemovedFromSelection",
+ "sm3"
+ );
+ await runPython(`pattern.RemoveFromSelection()`);
+ await waitForUiaEvent();
+ ok(true, "sm3 got ElementRemovedFromSelection event");
+ await testSelectionItemProps("sm3", false, "selectMulti");
+
+ await testSelectionItemProps("t1", false, "tablist");
+ await testSelectionItemProps("t2", true, "tablist");
+
+ // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern on grid
+ // cells.
+ if (gIsUiaEnabled) {
+ await testSelectionItemProps("g1", false, "grid");
+ await testSelectionItemProps("g2", true, "grid");
+ }
+
+ await testSelectionItemProps("r1", true, null);
+ await testSelectionItemProps("r2", false, null);
+ // The IA2 -> UIA proxy doesn't fire correct events for radio buttons.
+ if (gIsUiaEnabled) {
+ info("Calling Select on r2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "r2");
+ await runPython(`pattern.Select()`);
+ await waitForUiaEvent();
+ ok(true, "r2 got ElementSelected event");
+ await testSelectionItemProps("r1", false, null);
+ await testSelectionItemProps("r2", true, null);
+ info("Calling RemoveFromSelection on r2");
+ await testPythonRaises(
+ `pattern.RemoveFromSelection()`,
+ "RemoveFromSelection failed on r2"
+ );
+ }
+
+ await testPatternAbsent("m1", "SelectionItem");
+ // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern for radio
+ // menu items.
+ if (gIsUiaEnabled) {
+ await testSelectionItemProps("m2", false, null);
+ await testSelectionItemProps("m3", true, null);
+ }
+
+ await testPatternAbsent("button", "SelectionItem");
+});
diff --git a/accessible/tests/browser/windows/uia/browser_simplePatterns.js b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
index f464db0e13..484d217af2 100644
--- a/accessible/tests/browser/windows/uia/browser_simplePatterns.js
+++ b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
@@ -27,6 +27,7 @@ addUiaTask(
<button id="button">button</button>
<p id="p">p</p>
<input id="checkbox" type="checkbox">
+<input id="radio" type="radio">
`,
async function testInvoke() {
await definePyVar("doc", `getDocUia()`);
@@ -54,6 +55,8 @@ addUiaTask(
// Check boxes expose the Toggle pattern, so they should not expose the
// Invoke pattern.
await testPatternAbsent("checkbox", "Invoke");
+ // Ditto for radio buttons.
+ await testPatternAbsent("radio", "Invoke");
}
}
);
@@ -317,6 +320,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "text");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
+ ok(true, "Got ValueValue prop change event on text");
is(
await runPython(`pattern.CurrentValue`),
"after",
@@ -434,6 +438,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
+ ok(true, "Got ValueValue prop change event on ariaTextbox");
is(
await runPython(`pattern.CurrentValue`),
"after",
@@ -443,3 +448,95 @@ addUiaTask(
await testPatternAbsent("button", "Value");
}
);
+
+async function testRangeValueProps(id, ro, val, min, max, small, large) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`);
+ ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`);
+ is(
+ !!(await runPython(`pattern.CurrentIsReadOnly`)),
+ ro,
+ `${id} has IsReadOnly ${ro}`
+ );
+ is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`);
+ is(
+ await runPython(`pattern.CurrentMinimum`),
+ min,
+ `${id} has correct Minimum`
+ );
+ is(
+ await runPython(`pattern.CurrentMaximum`),
+ max,
+ `${id} has correct Maximum`
+ );
+ // IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't
+ // either.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(`pattern.CurrentSmallChange`),
+ small,
+ `${id} has correct SmallChange`
+ );
+ is(
+ await runPython(`pattern.CurrentLargeChange`),
+ large,
+ `${id} has correct LargeChange`
+ );
+ }
+}
+
+/**
+ * Test the RangeValue pattern.
+ */
+addUiaTask(
+ `
+<input id="range" type="range">
+<input id="rangeBig" type="range" max="1000">
+<progress id="progress" value="0.5"></progress>
+<input id="numberRo" type="number" min="0" max="10" value="5" readonly>
+<div id="ariaSlider" role="slider">slider</div>
+<button id="button">button</button>
+ `,
+ async function testRangeValue(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testRangeValueProps("range", false, 50, 0, 100, 1, 10);
+ info("SetValue on range");
+ await setUpWaitForUiaPropEvent("RangeValueValue", "range");
+ await runPython(`pattern.SetValue(20)`);
+ await waitForUiaEvent();
+ ok(true, "Got RangeValueValue prop change event on range");
+ is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value");
+
+ await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100);
+
+ // Gecko a11y doesn't expose progress bars as read only, but it probably
+ // should.
+ await testRangeValueProps("progress", false, 0.5, 0, 1, 0, 0.1);
+ info("Calling SetValue on progress");
+ await testPythonRaises(
+ `pattern.SetValue(0.6)`,
+ "SetValue on progress failed"
+ );
+
+ await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1);
+ info("Calling SetValue on numberRo");
+ await testPythonRaises(
+ `pattern.SetValue(6)`,
+ "SetValue on numberRo failed"
+ );
+
+ await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null);
+ info("Setting aria-valuenow on ariaSlider");
+ await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider");
+ await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60");
+ await waitForUiaEvent();
+ ok(true, "Got RangeValueValue prop change event on ariaSlider");
+ is(
+ await runPython(`pattern.CurrentValue`),
+ 60,
+ "ariaSlider has correct Value"
+ );
+
+ await testPatternAbsent("button", "RangeValue");
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js
index 5b453ce6fe..217b8cb844 100644
--- a/accessible/tests/browser/windows/uia/head.js
+++ b/accessible/tests/browser/windows/uia/head.js
@@ -4,7 +4,7 @@
"use strict";
-/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises */
+/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises, isUiaElementArray */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
@@ -126,3 +126,15 @@ async function testPythonRaises(expression, message) {
}
ok(failed, message);
}
+
+/**
+ * Verify that an array of UIA elements contains (only) elements with the given
+ * DOM ids.
+ */
+async function isUiaElementArray(pyExpr, ids, message) {
+ const result = await runPython(`
+ uias = (${pyExpr})
+ return [uias.GetElement(i).CurrentAutomationId for i in range(uias.Length)]
+ `);
+ SimpleTest.isDeeply(result, ids, message);
+}
diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js
index ebb5a54b85..65afebd83a 100644
--- a/accessible/tests/mochitest/attributes.js
+++ b/accessible/tests/mochitest/attributes.js
@@ -295,13 +295,10 @@ const kBoldFontWeight = function equalsToBold(aWeight) {
return aWeight > 400;
};
-let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled");
// The pt font size of the input element can vary by Linux distro.
const kInputFontSize =
- WIN || (MAC && isNNT)
+ WIN || MAC
? "10pt"
- : MAC
- ? "8pt"
: function () {
return true;
};
diff --git a/accessible/tests/mochitest/role/test_dpub_aria.html b/accessible/tests/mochitest/role/test_dpub_aria.html
index 621c86a59b..8294669caf 100644
--- a/accessible/tests/mochitest/role/test_dpub_aria.html
+++ b/accessible/tests/mochitest/role/test_dpub_aria.html
@@ -36,7 +36,7 @@
testRole("doc-epigraph", ROLE_SECTION);
testRole("doc-epilogue", ROLE_LANDMARK);
testRole("doc-errata", ROLE_LANDMARK);
- testRole("doc-example", ROLE_SECTION);
+ testRole("doc-example", ROLE_FIGURE);
testRole("doc-footnote", ROLE_FOOTNOTE);
testRole("doc-foreword", ROLE_LANDMARK);
testRole("doc-glossary", ROLE_LANDMARK);
@@ -46,6 +46,8 @@
testRole("doc-noteref", ROLE_LINK);
testRole("doc-notice", ROLE_NOTE);
testRole("doc-pagebreak", ROLE_SEPARATOR);
+ testRole("doc-pagefooter", ROLE_SECTION);
+ testRole("doc-pageheader", ROLE_SECTION);
testRole("doc-pagelist", ROLE_NAVIGATION);
testRole("doc-part", ROLE_LANDMARK);
testRole("doc-preface", ROLE_LANDMARK);
@@ -101,6 +103,8 @@
<div id="doc-noteref" role="doc-noteref">noteref</div>
<div id="doc-notice" role="doc-notice">notice</div>
<div id="doc-pagebreak" role="doc-pagebreak">pagebreak</div>
+ <div id="doc-pagefooter" role="doc-pagefooter">pagefooter</div>
+ <div id="doc-pageheader" role="doc-pageheader">pageheader</div>
<div id="doc-pagelist" role="doc-pagelist">pagelist</div>
<div id="doc-part" role="doc-part">part</div>
<div id="doc-preface" role="doc-preface">preface</div>
diff --git a/accessible/windows/ia2/ia2AccessibleTable.cpp b/accessible/windows/ia2/ia2AccessibleTable.cpp
index 50bdc79967..9ce94e8348 100644
--- a/accessible/windows/ia2/ia2AccessibleTable.cpp
+++ b/accessible/windows/ia2/ia2AccessibleTable.cpp
@@ -21,7 +21,7 @@
using namespace mozilla::a11y;
TableAccessible* ia2AccessibleTable::TableAcc() {
- Accessible* acc = Acc();
+ Accessible* acc = MsaaAccessible::Acc();
return acc ? acc->AsTable() : nullptr;
}
@@ -46,6 +46,18 @@ ia2AccessibleTable::QueryInterface(REFIID iid, void** ppv) {
return S_OK;
}
+ if (IID_IGridProvider == iid) {
+ *ppv = static_cast<IGridProvider*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ if (IID_ITableProvider == iid) {
+ *ppv = static_cast<ITableProvider*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
return ia2AccessibleHypertext::QueryInterface(iid, ppv);
}
diff --git a/accessible/windows/ia2/ia2AccessibleTable.h b/accessible/windows/ia2/ia2AccessibleTable.h
index 622187c379..e8479b1733 100644
--- a/accessible/windows/ia2/ia2AccessibleTable.h
+++ b/accessible/windows/ia2/ia2AccessibleTable.h
@@ -12,6 +12,7 @@
#include "AccessibleTable2.h"
#include "ia2AccessibleHypertext.h"
#include "IUnknownImpl.h"
+#include "UiaGrid.h"
namespace mozilla {
namespace a11y {
@@ -20,6 +21,7 @@ class TableAccessible;
class ia2AccessibleTable : public IAccessibleTable,
public IAccessibleTable2,
+ public UiaGrid,
public ia2AccessibleHypertext {
public:
// IUnknown
diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.cpp b/accessible/windows/ia2/ia2AccessibleTableCell.cpp
index 0204983a08..c8e98d6a2f 100644
--- a/accessible/windows/ia2/ia2AccessibleTableCell.cpp
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.cpp
@@ -27,6 +27,8 @@ TableCellAccessible* ia2AccessibleTableCell::CellAcc() {
// IUnknown
IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleTableCell)
IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleTableCell)
+IMPL_IUNKNOWN_QUERY_IFACE(IGridItemProvider)
+IMPL_IUNKNOWN_QUERY_IFACE(ITableItemProvider)
IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext)
////////////////////////////////////////////////////////////////////////////////
diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.h b/accessible/windows/ia2/ia2AccessibleTableCell.h
index 04e978cb96..6925cd7b17 100644
--- a/accessible/windows/ia2/ia2AccessibleTableCell.h
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.h
@@ -11,12 +11,14 @@
#include "AccessibleTableCell.h"
#include "ia2AccessibleHypertext.h"
#include "IUnknownImpl.h"
+#include "UiaGridItem.h"
namespace mozilla {
namespace a11y {
class TableCellAccessible;
class ia2AccessibleTableCell : public IAccessibleTableCell,
+ public UiaGridItem,
public ia2AccessibleHypertext {
public:
// IUnknown
diff --git a/accessible/windows/msaa/IUnknownImpl.h b/accessible/windows/msaa/IUnknownImpl.h
index 7935ebedda..757bff44bf 100644
--- a/accessible/windows/msaa/IUnknownImpl.h
+++ b/accessible/windows/msaa/IUnknownImpl.h
@@ -47,31 +47,31 @@ class AutoRefCnt {
} // namespace a11y
} // namespace mozilla
-#define DECL_IUNKNOWN \
- public: \
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); \
- ULONG STDMETHODCALLTYPE AddRef() override { \
- MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
- ++mRefCnt; \
- return mRefCnt; \
- } \
- ULONG STDMETHODCALLTYPE Release() override { \
- MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
- --mRefCnt; \
- if (mRefCnt) return mRefCnt; \
- \
- delete this; \
- return 0; \
- } \
- \
- private: \
- mozilla::a11y::AutoRefCnt mRefCnt; \
- \
+#define DECL_IUNKNOWN \
+ public: \
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**) override; \
+ ULONG STDMETHODCALLTYPE AddRef() override { \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ ++mRefCnt; \
+ return mRefCnt; \
+ } \
+ ULONG STDMETHODCALLTYPE Release() override { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ --mRefCnt; \
+ if (mRefCnt) return mRefCnt; \
+ \
+ delete this; \
+ return 0; \
+ } \
+ \
+ private: \
+ mozilla::a11y::AutoRefCnt mRefCnt; \
+ \
public:
#define DECL_IUNKNOWN_INHERITED \
public: \
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**) override;
#define IMPL_IUNKNOWN_QUERY_HEAD(Class) \
STDMETHODIMP \
diff --git a/accessible/windows/msaa/LazyInstantiator.cpp b/accessible/windows/msaa/LazyInstantiator.cpp
index 61814d059a..3ace26d9c0 100644
--- a/accessible/windows/msaa/LazyInstantiator.cpp
+++ b/accessible/windows/msaa/LazyInstantiator.cpp
@@ -12,6 +12,7 @@
#include "mozilla/a11y/Platform.h"
#include "mozilla/Assertions.h"
#include "mozilla/mscom/ProcessRuntime.h"
+#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "MsaaRootAccessible.h"
@@ -41,9 +42,9 @@ static const wchar_t kLazyInstantiatorProp[] =
Maybe<bool> LazyInstantiator::sShouldBlockUia;
-/* static */
-already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
- RefPtr<IAccessible> result;
+template <class T>
+already_AddRefed<T> LazyInstantiator::GetRoot(HWND aHwnd) {
+ RefPtr<T> result;
// At this time we only want to check whether the acc service is running. We
// don't actually want to create the acc service yet.
if (!GetAccService()) {
@@ -80,7 +81,7 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
if (!rootAcc->IsRoot()) {
// rootAcc might represent a popup as opposed to a true root accessible.
// In that case we just use the regular LocalAccessible::GetNativeInterface.
- rootAcc->GetNativeInterface(getter_AddRefs(result));
+ result = MsaaAccessible::GetFrom(rootAcc);
return result.forget();
}
@@ -90,7 +91,7 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
// don't need LazyInstantiator's capabilities anymore (since a11y is already
// running). We can bypass LazyInstantiator by retrieving the internal
// unknown (which is not wrapped by the LazyInstantiator) and then querying
- // that for IID_IAccessible.
+ // that for the interface we want.
RefPtr<IUnknown> punk(msaaRoot->GetInternalUnknown());
MOZ_ASSERT(punk);
@@ -98,10 +99,24 @@ already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
return nullptr;
}
- punk->QueryInterface(IID_IAccessible, getter_AddRefs(result));
+ punk->QueryInterface(__uuidof(T), getter_AddRefs(result));
return result.forget();
}
+/* static */
+already_AddRefed<IAccessible> LazyInstantiator::GetRootAccessible(HWND aHwnd) {
+ return GetRoot<IAccessible>(aHwnd);
+}
+
+/* static */
+already_AddRefed<IRawElementProviderSimple> LazyInstantiator::GetRootUia(
+ HWND aHwnd) {
+ if (!StaticPrefs::accessibility_uia_enable()) {
+ return nullptr;
+ }
+ return GetRoot<IRawElementProviderSimple>(aHwnd);
+}
+
/**
* When marshaling an interface, COM makes a whole bunch of QueryInterface
* calls to determine what kind of marshaling the interface supports. We need
@@ -135,7 +150,8 @@ LazyInstantiator::LazyInstantiator(HWND aHwnd)
mAllowBlindAggregation(false),
mWeakMsaaRoot(nullptr),
mWeakAccessible(nullptr),
- mWeakDispatch(nullptr) {
+ mWeakDispatch(nullptr),
+ mWeakUia(nullptr) {
MOZ_ASSERT(aHwnd);
// Assign ourselves as the designated LazyInstantiator for aHwnd
DebugOnly<BOOL> setPropOk =
@@ -374,9 +390,16 @@ LazyInstantiator::MaybeResolveRoot() {
if (FAILED(hr)) {
return hr;
}
-
// mWeakAccessible is weak, so don't hold a strong ref
mWeakAccessible->Release();
+ if (StaticPrefs::accessibility_uia_enable()) {
+ hr = mRealRootUnk->QueryInterface(IID_IRawElementProviderSimple,
+ (void**)&mWeakUia);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ mWeakUia->Release();
+ }
// Now that a11y is running, we don't need to remain registered with our
// HWND anymore.
@@ -401,6 +424,9 @@ IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible)
IMPL_IUNKNOWN_QUERY_IFACE(IAccessible)
IMPL_IUNKNOWN_QUERY_IFACE(IDispatch)
IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
+if (StaticPrefs::accessibility_uia_enable()) {
+ IMPL_IUNKNOWN_QUERY_IFACE(IRawElementProviderSimple)
+}
// See EnableBlindAggregation for comments.
if (!mAllowBlindAggregation) {
return E_NOINTERFACE;
@@ -771,5 +797,45 @@ LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid,
return servProv->QueryService(aServiceId, aServiceIid, aOutInterface);
}
+STDMETHODIMP
+LazyInstantiator::get_ProviderOptions(
+ __RPC__out enum ProviderOptions* aOptions) {
+ // This method is called before a UIA connection is fully established and thus
+ // before we can detect the client. We must not call RESOLVE_ROOT here because
+ // this might turn out to be a client we want to block.
+ if (!aOptions) {
+ return E_INVALIDARG;
+ }
+ *aOptions = uiaRawElmProvider::kProviderOptions;
+ return S_OK;
+}
+
+STDMETHODIMP
+LazyInstantiator::GetPatternProvider(
+ PATTERNID aPatternId, __RPC__deref_out_opt IUnknown** aPatternProvider) {
+ RESOLVE_ROOT;
+ return mWeakUia->GetPatternProvider(aPatternId, aPatternProvider);
+}
+
+STDMETHODIMP
+LazyInstantiator::GetPropertyValue(PROPERTYID aPropertyId,
+ __RPC__out VARIANT* aPropertyValue) {
+ RESOLVE_ROOT;
+ return mWeakUia->GetPropertyValue(aPropertyId, aPropertyValue);
+}
+
+STDMETHODIMP
+LazyInstantiator::get_HostRawElementProvider(
+ __RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider) {
+ // This method is called before a UIA connection is fully established and thus
+ // before we can detect the client. We must not call RESOLVE_ROOT here because
+ // this might turn out to be a client we want to block.
+ if (!aRawElmProvider) {
+ return E_INVALIDARG;
+ }
+ *aRawElmProvider = nullptr;
+ return UiaHostProviderFromHwnd(mHwnd, aRawElmProvider);
+}
+
} // namespace a11y
} // namespace mozilla
diff --git a/accessible/windows/msaa/LazyInstantiator.h b/accessible/windows/msaa/LazyInstantiator.h
index 00fa4ba6ed..adbb0f70b5 100644
--- a/accessible/windows/msaa/LazyInstantiator.h
+++ b/accessible/windows/msaa/LazyInstantiator.h
@@ -13,6 +13,7 @@
#include "nsString.h"
#include <oleacc.h>
+#include <uiautomation.h>
class nsIFile;
@@ -29,10 +30,14 @@ class MsaaRootAccessible;
* services in order to fulfill; and
* (2) LazyInstantiator::ShouldInstantiate returns true.
*/
-class LazyInstantiator final : public IAccessible, public IServiceProvider {
+class LazyInstantiator final : public IAccessible,
+ public IServiceProvider,
+ public IRawElementProviderSimple {
public:
[[nodiscard]] static already_AddRefed<IAccessible> GetRootAccessible(
HWND aHwnd);
+ [[nodiscard]] static already_AddRefed<IRawElementProviderSimple> GetRootUia(
+ HWND aHwnd);
static void EnableBlindAggregation(HWND aHwnd);
// IUnknown
@@ -83,6 +88,22 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aServiceIid,
void** aOutInterface) override;
+ // IRawElementProviderSimple
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ProviderOptions(
+ /* [retval][out] */ __RPC__out enum ProviderOptions* aProviderOptions);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPatternProvider(
+ /* [in] */ PATTERNID aPatternId,
+ /* [retval][out] */ __RPC__deref_out_opt IUnknown** aPatternProvider);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
+ /* [in] */ PROPERTYID aPropertyId,
+ /* [retval][out] */ __RPC__out VARIANT* aPropertyValue);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_HostRawElementProvider(
+ /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple**
+ aRawElmProvider);
+
/**
* We cache the result of UIA detection because it could be expensive if a
* client repeatedly queries us. This function is called to reset that cache
@@ -117,6 +138,9 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
void TransplantRefCnt();
void ClearProp();
+ template <class T>
+ static already_AddRefed<T> GetRoot(HWND aHwnd);
+
private:
mozilla::a11y::AutoRefCnt mRefCnt;
HWND mHwnd;
@@ -133,6 +157,7 @@ class LazyInstantiator final : public IAccessible, public IServiceProvider {
MsaaRootAccessible* mWeakMsaaRoot;
IAccessible* mWeakAccessible;
IDispatch* mWeakDispatch;
+ IRawElementProviderSimple* mWeakUia;
static Maybe<bool> sShouldBlockUia;
};
diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp
index f4d1c7b176..eaf9e7c126 100644
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -129,6 +129,7 @@ void a11y::PlatformShowHideEvent(Accessible* aTarget, Accessible*, bool aInsert,
void a11y::PlatformSelectionEvent(Accessible* aTarget, Accessible*,
uint32_t aType) {
MsaaAccessible::FireWinEvent(aTarget, aType);
+ uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aType);
}
static bool GetInstantiatorExecutable(const DWORD aPid,
diff --git a/accessible/windows/uia/UiaGrid.cpp b/accessible/windows/uia/UiaGrid.cpp
new file mode 100644
index 0000000000..2a22a1f4da
--- /dev/null
+++ b/accessible/windows/uia/UiaGrid.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ia2AccessibleTable.h"
+#include "mozilla/a11y/TableAccessible.h"
+#include "nsIAccessiblePivot.h"
+#include "Pivot.h"
+#include "UiaGrid.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// Helpers
+
+// Used to search for all row and column headers in a table. This could be slow,
+// as it potentially walks all cells in the table. However, it's unclear if,
+// when or how often clients will use this. If this proves to be a performance
+// problem, we will need to add methods to TableAccessible to get all row and
+// column headers in a faster way.
+class HeaderRule : public PivotRule {
+ public:
+ explicit HeaderRule(role aRole) : mRole(aRole) {}
+
+ virtual uint16_t Match(Accessible* aAcc) override {
+ role accRole = aAcc->Role();
+ if (accRole == mRole) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ if (accRole == roles::CAPTION || aAcc->IsTableCell()) {
+ return nsIAccessibleTraversalRule::FILTER_IGNORE |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+ }
+
+ private:
+ role mRole;
+};
+
+static SAFEARRAY* GetAllHeaders(Accessible* aTable, role aRole) {
+ AutoTArray<Accessible*, 20> headers;
+ Pivot pivot(aTable);
+ HeaderRule rule(aRole);
+ for (Accessible* header = pivot.Next(aTable, rule); header;
+ header = pivot.Next(header, rule)) {
+ headers.AppendElement(header);
+ }
+ return AccessibleArrayToUiaArray(headers);
+}
+
+// UiaGrid
+
+Accessible* UiaGrid::Acc() {
+ auto* ia2t = static_cast<ia2AccessibleTable*>(this);
+ return ia2t->MsaaAccessible::Acc();
+}
+
+TableAccessible* UiaGrid::TableAcc() {
+ Accessible* acc = Acc();
+ return acc ? acc->AsTable() : nullptr;
+}
+
+// IGridProvider methods
+
+STDMETHODIMP
+UiaGrid::GetItem(int aRow, int aColumn,
+ __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ TableAccessible* table = TableAcc();
+ if (!table) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ Accessible* cell = table->CellAt(aRow, aColumn);
+ if (!cell) {
+ return E_INVALIDARG;
+ }
+ RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(cell);
+ uia.forget(aRetVal);
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGrid::get_RowCount(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableAccessible* table = TableAcc();
+ if (!table) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = table->RowCount();
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGrid::get_ColumnCount(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableAccessible* table = TableAcc();
+ if (!table) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = table->ColCount();
+ return S_OK;
+}
+
+// ITableProvider methods
+
+STDMETHODIMP
+UiaGrid::GetRowHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = GetAllHeaders(acc, roles::ROWHEADER);
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGrid::GetColumnHeaders(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = GetAllHeaders(acc, roles::COLUMNHEADER);
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGrid::get_RowOrColumnMajor(__RPC__out enum RowOrColumnMajor* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ // HTML and ARIA tables are always in row major order.
+ *aRetVal = RowOrColumnMajor_RowMajor;
+ return S_OK;
+}
diff --git a/accessible/windows/uia/UiaGrid.h b/accessible/windows/uia/UiaGrid.h
new file mode 100644
index 0000000000..c38442246c
--- /dev/null
+++ b/accessible/windows/uia/UiaGrid.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_UiaGrid_h__
+#define mozilla_a11y_UiaGrid_h__
+
+#include "objbase.h"
+#include "uiautomation.h"
+
+namespace mozilla::a11y {
+class Accessible;
+class TableAccessible;
+
+/**
+ * IGridProvider and ITableProvider implementations.
+ */
+class UiaGrid : public IGridProvider, public ITableProvider {
+ public:
+ // IGridProvider
+ virtual HRESULT STDMETHODCALLTYPE GetItem(
+ /* [in] */ int aRow,
+ /* [in] */ int aColumn,
+ /* [retval][out] */
+ __RPC__deref_out_opt IRawElementProviderSimple** aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowCount(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ColumnCount(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ // ITableProvider
+ virtual HRESULT STDMETHODCALLTYPE GetRowHeaders(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
+
+ virtual HRESULT STDMETHODCALLTYPE GetColumnHeaders(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowOrColumnMajor(
+ /* [retval][out] */ __RPC__out enum RowOrColumnMajor* aRetVal);
+
+ private:
+ Accessible* Acc();
+ TableAccessible* TableAcc();
+};
+
+} // namespace mozilla::a11y
+
+#endif
diff --git a/accessible/windows/uia/UiaGridItem.cpp b/accessible/windows/uia/UiaGridItem.cpp
new file mode 100644
index 0000000000..89c56b8d45
--- /dev/null
+++ b/accessible/windows/uia/UiaGridItem.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ia2AccessibleTableCell.h"
+#include "mozilla/a11y/TableAccessible.h"
+#include "mozilla/a11y/TableCellAccessible.h"
+#include "UiaGridItem.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// UiaGridItem
+
+TableCellAccessible* UiaGridItem::CellAcc() {
+ auto* derived = static_cast<ia2AccessibleTableCell*>(this);
+ Accessible* acc = derived->Acc();
+ return acc ? acc->AsTableCell() : nullptr;
+}
+
+// IGridItemProvider methods
+
+STDMETHODIMP
+UiaGridItem::get_Row(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = cell->RowIdx();
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGridItem::get_Column(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = cell->ColIdx();
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGridItem::get_RowSpan(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = cell->RowExtent();
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGridItem::get_ColumnSpan(__RPC__out int* aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ *aRetVal = cell->ColExtent();
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGridItem::get_ContainingGrid(
+ __RPC__deref_out_opt IRawElementProviderSimple** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ TableAccessible* table = cell->Table();
+ if (!table) {
+ return E_FAIL;
+ }
+ Accessible* tableAcc = table->AsAccessible();
+ RefPtr<IRawElementProviderSimple> uia = MsaaAccessible::GetFrom(tableAcc);
+ uia.forget(aRetVal);
+ return S_OK;
+}
+
+// ITableItemProvider methods
+
+STDMETHODIMP
+UiaGridItem::GetRowHeaderItems(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AutoTArray<Accessible*, 10> cells;
+ cell->RowHeaderCells(&cells);
+ *aRetVal = AccessibleArrayToUiaArray(cells);
+ return S_OK;
+}
+
+STDMETHODIMP
+UiaGridItem::GetColumnHeaderItems(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
+ if (!aRetVal) {
+ return E_INVALIDARG;
+ }
+ *aRetVal = nullptr;
+ TableCellAccessible* cell = CellAcc();
+ if (!cell) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ AutoTArray<Accessible*, 10> cells;
+ cell->ColHeaderCells(&cells);
+ *aRetVal = AccessibleArrayToUiaArray(cells);
+ return S_OK;
+}
diff --git a/accessible/windows/uia/UiaGridItem.h b/accessible/windows/uia/UiaGridItem.h
new file mode 100644
index 0000000000..6c59630b0f
--- /dev/null
+++ b/accessible/windows/uia/UiaGridItem.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_UiaGridItem_h__
+#define mozilla_a11y_UiaGridItem_h__
+
+#include "objbase.h"
+#include "uiautomation.h"
+
+namespace mozilla::a11y {
+class TableCellAccessible;
+
+/**
+ * IGridItemProvider and ITableItemProvider implementations.
+ */
+class UiaGridItem : public IGridItemProvider, public ITableItemProvider {
+ public:
+ // IGridItemProvider
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Row(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Column(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_RowSpan(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ColumnSpan(
+ /* [retval][out] */ __RPC__out int* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ContainingGrid(
+ /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple**
+ aRetVal);
+
+ // ITableItemProvider
+ virtual HRESULT STDMETHODCALLTYPE GetRowHeaderItems(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
+
+ virtual HRESULT STDMETHODCALLTYPE GetColumnHeaderItems(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
+
+ private:
+ TableCellAccessible* CellAcc();
+};
+
+} // namespace mozilla::a11y
+
+#endif
diff --git a/accessible/windows/uia/moz.build b/accessible/windows/uia/moz.build
index c52a24d612..2061261494 100644
--- a/accessible/windows/uia/moz.build
+++ b/accessible/windows/uia/moz.build
@@ -5,6 +5,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
SOURCES += [
+ "UiaGrid.cpp",
+ "UiaGridItem.cpp",
"uiaRawElmProvider.cpp",
"UiaRoot.cpp",
]
@@ -13,6 +15,7 @@ LOCAL_INCLUDES += [
"/accessible/base",
"/accessible/generic",
"/accessible/html",
+ "/accessible/windows/ia2",
"/accessible/windows/msaa",
"/accessible/xpcom",
"/accessible/xul",
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;
+}
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
index 0e05d1a030..ea713d8bc1 100644
--- a/accessible/windows/uia/uiaRawElmProvider.h
+++ b/accessible/windows/uia/uiaRawElmProvider.h
@@ -11,10 +11,20 @@
#include <stdint.h>
#include <uiautomation.h>
+#include <initializer_list>
+
+#include "nsString.h"
+
+template <class T>
+class nsTArray;
+template <class T>
+class RefPtr;
+
namespace mozilla {
namespace a11y {
class Accessible;
+enum class RelationType;
/**
* IRawElementProviderSimple implementation (maintains IAccessibleEx approach).
@@ -26,8 +36,16 @@ class uiaRawElmProvider : public IAccessibleEx,
public IToggleProvider,
public IExpandCollapseProvider,
public IScrollItemProvider,
- public IValueProvider {
+ public IValueProvider,
+ public IRangeValueProvider,
+ public ISelectionProvider,
+ public ISelectionItemProvider {
public:
+ static constexpr enum ProviderOptions kProviderOptions =
+ static_cast<enum ProviderOptions>(ProviderOptions_ServerSideProvider |
+ ProviderOptions_UseComThreading |
+ ProviderOptions_HasNativeIAccessible);
+
static void RaiseUiaEventForGeckoEvent(Accessible* aAcc,
uint32_t aGeckoEvent);
static void RaiseUiaEventForStateChange(Accessible* aAcc, uint64_t aState,
@@ -118,6 +136,51 @@ class uiaRawElmProvider : public IAccessibleEx,
virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsReadOnly(
/* [retval][out] */ __RPC__out BOOL* pRetVal);
+ // IRangeValueProvider
+ virtual HRESULT STDMETHODCALLTYPE SetValue(
+ /* [in] */ double aVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Value(
+ /* [retval][out] */ __RPC__out double* aRetVal);
+
+ // get_IsReadOnly is shared with IValueProvider.
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Maximum(
+ /* [retval][out] */ __RPC__out double* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Minimum(
+ /* [retval][out] */ __RPC__out double* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_LargeChange(
+ /* [retval][out] */ __RPC__out double* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SmallChange(
+ /* [retval][out] */ __RPC__out double* aRetVal);
+
+ // ISelectionProvider
+ virtual HRESULT STDMETHODCALLTYPE GetSelection(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(
+ /* [retval][out] */ __RPC__out BOOL* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(
+ /* [retval][out] */ __RPC__out BOOL* aRetVal);
+
+ // ISelectionItemProvider methods
+ virtual HRESULT STDMETHODCALLTYPE Select(void);
+
+ virtual HRESULT STDMETHODCALLTYPE AddToSelection(void);
+
+ virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection(void);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_IsSelected(
+ /* [retval][out] */ __RPC__out BOOL* aRetVal);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_SelectionContainer(
+ /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple**
+ aRetVal);
+
private:
Accessible* Acc() const;
bool IsControl();
@@ -125,8 +188,18 @@ class uiaRawElmProvider : public IAccessibleEx,
bool HasTogglePattern();
bool HasExpandCollapsePattern();
bool HasValuePattern() const;
+ template <class Derived, class Interface>
+ RefPtr<Interface> GetPatternFromDerived();
+ bool HasSelectionItemPattern();
+ SAFEARRAY* AccRelationsToUiaArray(
+ std::initializer_list<RelationType> aTypes) const;
+ Accessible* GetLabeledBy() const;
+ long GetLandmarkType() const;
+ void GetLocalizedLandmarkType(nsAString& aLocalized) const;
};
+SAFEARRAY* AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs);
+
} // namespace a11y
} // namespace mozilla
diff --git a/accessible/xpcom/xpcAccessibilityService.cpp b/accessible/xpcom/xpcAccessibilityService.cpp
index a7b70b6f33..f6692bffbb 100644
--- a/accessible/xpcom/xpcAccessibilityService.cpp
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -123,7 +123,8 @@ xpcAccessibilityService::GetAccessibleFor(nsINode* aNode,
DocAccessible* document = accService->GetDocAccessible(aNode->OwnerDoc());
if (document) {
- NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(aNode)));
+ NS_IF_ADDREF(*aAccessible =
+ ToXPC(document->GetAccessibleEvenIfNotInMap(aNode)));
}
return NS_OK;