summaryrefslogtreecommitdiffstats
path: root/accessible
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
commitfbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch)
tree4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /accessible
parentReleasing progress-linux version 124.0.1-1~progress7.99u1. (diff)
downloadfirefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz
firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible')
-rw-r--r--accessible/android/AccessibleWrap.cpp2
-rw-r--r--accessible/android/Platform.cpp2
-rw-r--r--accessible/aom/moz.build4
-rw-r--r--accessible/atk/AccessibleWrap.cpp2
-rw-r--r--accessible/base/ARIAMap.cpp23
-rw-r--r--accessible/base/ARIAMap.h13
-rw-r--r--accessible/base/AccGroupInfo.cpp1
-rw-r--r--accessible/base/AccIterator.cpp60
-rw-r--r--accessible/base/AccIterator.h8
-rw-r--r--accessible/base/Asserts.cpp2
-rw-r--r--accessible/base/HTMLMarkupMap.h7
-rw-r--r--accessible/base/Platform.h4
-rw-r--r--accessible/base/RoleMap.h151
-rw-r--r--accessible/base/moz.build4
-rw-r--r--accessible/base/nsAccessibilityService.cpp24
-rw-r--r--accessible/base/nsAccessibilityService.h13
-rw-r--r--accessible/base/nsCoreUtils.cpp58
-rw-r--r--accessible/base/nsCoreUtils.h16
-rw-r--r--accessible/base/nsTextEquivUtils.cpp31
-rw-r--r--accessible/basetypes/Accessible.cpp22
-rw-r--r--accessible/basetypes/Accessible.h5
-rw-r--r--accessible/generic/BaseAccessibles.cpp3
-rw-r--r--accessible/generic/DocAccessible.cpp176
-rw-r--r--accessible/generic/DocAccessible.h59
-rw-r--r--accessible/generic/LocalAccessible.cpp70
-rw-r--r--accessible/generic/LocalAccessible.h18
-rw-r--r--accessible/generic/moz.build4
-rw-r--r--accessible/html/HTMLElementAccessibles.cpp33
-rw-r--r--accessible/html/HTMLElementAccessibles.h17
-rw-r--r--accessible/html/HTMLFormControlAccessible.cpp6
-rw-r--r--accessible/html/HTMLLinkAccessible.cpp11
-rw-r--r--accessible/html/HTMLLinkAccessible.h2
-rw-r--r--accessible/html/moz.build4
-rw-r--r--accessible/interfaces/nsIAccessibleRole.idl7
-rw-r--r--accessible/ios/.clang-format11
-rw-r--r--accessible/ios/AccessibleWrap.h47
-rw-r--r--accessible/ios/AccessibleWrap.mm49
-rw-r--r--accessible/ios/ApplicationAccessibleWrap.h20
-rw-r--r--accessible/ios/DocAccessibleWrap.h23
-rw-r--r--accessible/ios/MUIAccessible.h72
-rw-r--r--accessible/ios/MUIAccessible.mm497
-rw-r--r--accessible/ios/MUIRootAccessible.h29
-rw-r--r--accessible/ios/MUIRootAccessible.mm45
-rw-r--r--accessible/ios/MUIRootAccessibleProtocol.h51
-rw-r--r--accessible/ios/Platform.mm57
-rw-r--r--accessible/ios/RootAccessibleWrap.h42
-rw-r--r--accessible/ios/RootAccessibleWrap.mm50
-rw-r--r--accessible/ios/moz.build30
-rw-r--r--accessible/ipc/moz.build4
-rw-r--r--accessible/mac/GeckoTextMarker.mm19
-rw-r--r--accessible/mac/Platform.mm2
-rw-r--r--accessible/mac/mozAccessible.mm4
-rw-r--r--accessible/moz.build2
-rw-r--r--accessible/tests/browser/atk/browser_role.js2
-rw-r--r--accessible/tests/browser/atk/browser_table.js2
-rw-r--r--accessible/tests/browser/bounds/browser_accessible_moved.js8
-rw-r--r--accessible/tests/browser/bounds/browser_caret_rect.js2
-rw-r--r--accessible/tests/browser/bounds/browser_test_display_contents.js6
-rw-r--r--accessible/tests/browser/bounds/browser_test_iframe_transform.js4
-rw-r--r--accessible/tests/browser/bounds/browser_test_simple_transform.js6
-rw-r--r--accessible/tests/browser/bounds/browser_test_zoom.js6
-rw-r--r--accessible/tests/browser/bounds/browser_zero_area.js8
-rw-r--r--accessible/tests/browser/browser_shutdown_acc_reference.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_doc_acc_reference.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js4
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js4
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_reference.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_parent_own_reference.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_proxy_acc_reference.js2
-rw-r--r--accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js4
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_own_reference.js2
-rw-r--r--accessible/tests/browser/e10s/browser.toml5
-rw-r--r--accessible/tests/browser/e10s/browser_aria_activedescendant.js485
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js2
-rw-r--r--accessible/tests/browser/e10s/browser_caching_large_update.js97
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js2
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations_002.js109
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js157
-rw-r--r--accessible/tests/browser/e10s/browser_caching_table.js2
-rw-r--r--accessible/tests/browser/e10s/browser_caching_text_bounds.js14
-rw-r--r--accessible/tests/browser/e10s/browser_file_input.js2
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js2
-rw-r--r--accessible/tests/browser/events/browser_test_docload.js2
-rw-r--r--accessible/tests/browser/events/browser_test_focus_browserui.js2
-rw-r--r--accessible/tests/browser/events/browser_test_focus_dialog.js2
-rw-r--r--accessible/tests/browser/events/browser_test_focus_urlbar.js4
-rw-r--r--accessible/tests/browser/events/browser_test_scrolling.js49
-rw-r--r--accessible/tests/browser/mac/browser_app.js38
-rw-r--r--accessible/tests/browser/mac/browser_bounds.js56
-rw-r--r--accessible/tests/browser/mac/browser_live_regions.js2
-rw-r--r--accessible/tests/browser/mac/browser_menulist.js2
-rw-r--r--accessible/tests/browser/mac/browser_roles_elements.js4
-rw-r--r--accessible/tests/browser/mac/browser_rotor.js8
-rw-r--r--accessible/tests/browser/mac/browser_text_leaf.js6
-rw-r--r--accessible/tests/browser/mac/browser_text_selection.js2
-rw-r--r--accessible/tests/browser/mac/browser_toggle_radio_check.js2
-rw-r--r--accessible/tests/browser/mac/browser_webarea.js10
-rw-r--r--accessible/tests/browser/scroll/browser_test_scroll_bounds.js14
-rw-r--r--accessible/tests/browser/scroll/browser_test_scroll_substring.js5
-rw-r--r--accessible/tests/browser/selectable/browser_test_select.js2
-rw-r--r--accessible/tests/browser/shared-head.js6
-rw-r--r--accessible/tests/browser/text/browser_text_paragraph_boundary.js2
-rw-r--r--accessible/tests/browser/text/head.js2
-rw-r--r--accessible/tests/browser/tree/browser_aria_owns.js2
-rw-r--r--accessible/tests/browser/tree/browser_browser_element.js2
-rw-r--r--accessible/tests/browser/tree/browser_lazy_tabs.js2
-rw-r--r--accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js4
-rw-r--r--accessible/tests/browser/windows/a11y_setup.py126
-rw-r--r--accessible/tests/browser/windows/ia2/browser.toml2
-rw-r--r--accessible/tests/browser/windows/ia2/browser_osPicker.js51
-rw-r--r--accessible/tests/browser/windows/ia2/browser_role.js2
-rw-r--r--accessible/tests/browser/windows/uia/browser.toml2
-rw-r--r--accessible/tests/browser/windows/uia/browser_controlType.js4
-rw-r--r--accessible/tests/browser/windows/uia/browser_elementFromPoint.js4
-rw-r--r--accessible/tests/browser/windows/uia/browser_tree.js104
-rw-r--r--accessible/tests/browser/windows/uia/head.js37
-rw-r--r--accessible/tests/crashtests/crashtests.list2
-rw-r--r--accessible/tests/mochitest/actions/test_media.html2
-rw-r--r--accessible/tests/mochitest/actions/test_tree.xhtml6
-rw-r--r--accessible/tests/mochitest/actions/test_treegrid.xhtml8
-rw-r--r--accessible/tests/mochitest/autocomplete.js8
-rw-r--r--accessible/tests/mochitest/common.js2
-rw-r--r--accessible/tests/mochitest/events.js7
-rw-r--r--accessible/tests/mochitest/events/a11y.toml2
-rw-r--r--accessible/tests/mochitest/events/test_contextmenu.html2
-rw-r--r--accessible/tests/mochitest/events/test_focus_aria_activedescendant.html327
-rw-r--r--accessible/tests/mochitest/events/test_focus_doc.html12
-rw-r--r--accessible/tests/mochitest/events/test_label.xhtml2
-rw-r--r--accessible/tests/mochitest/events/test_statechange.html2
-rw-r--r--accessible/tests/mochitest/name/markup.js2
-rw-r--r--accessible/tests/mochitest/name/test_tree.xhtml4
-rw-r--r--accessible/tests/mochitest/promisified-events.js4
-rw-r--r--accessible/tests/mochitest/relations/test_tabbrowser.xhtml2
-rw-r--r--accessible/tests/mochitest/role.js1
-rw-r--r--accessible/tests/mochitest/role/test_aria.html24
-rw-r--r--accessible/tests/mochitest/role/test_general.html2
-rw-r--r--accessible/tests/mochitest/states/test_aria_widgetitems.html4
-rw-r--r--accessible/tests/mochitest/test_bug420863.html4
-rw-r--r--accessible/tests/mochitest/text.js24
-rw-r--r--accessible/tests/mochitest/tree/test_aria_display_contents.html2
-rw-r--r--accessible/tests/mochitest/tree/test_aria_grid.html12
-rw-r--r--accessible/tests/mochitest/tree/test_aria_owns.html4
-rw-r--r--accessible/tests/mochitest/tree/test_tabbrowser.xhtml2
-rw-r--r--accessible/tests/mochitest/tree/test_tree.xhtml2
-rw-r--r--accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml4
-rw-r--r--accessible/tests/mochitest/treeupdate/test_delayed_removal.html2
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shadow_slots.html10
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shutdown.xhtml2
-rw-r--r--accessible/tests/mochitest/treeview.js16
-rw-r--r--accessible/windows/ia2/ia2Accessible.cpp2
-rw-r--r--accessible/windows/ia2/ia2AccessibleAction.cpp1
-rw-r--r--accessible/windows/ia2/ia2AccessibleComponent.cpp1
-rw-r--r--accessible/windows/ia2/ia2AccessibleHyperlink.cpp3
-rw-r--r--accessible/windows/ia2/ia2AccessibleValue.cpp1
-rw-r--r--accessible/windows/ia2/moz.build1
-rw-r--r--accessible/windows/msaa/AccessibleWrap.cpp43
-rw-r--r--accessible/windows/msaa/AccessibleWrap.h8
-rw-r--r--accessible/windows/msaa/ApplicationAccessibleWrap.cpp1
-rw-r--r--accessible/windows/msaa/Compatibility.cpp57
-rw-r--r--accessible/windows/msaa/Compatibility.h19
-rw-r--r--accessible/windows/msaa/LazyInstantiator.cpp6
-rw-r--r--accessible/windows/msaa/MsaaAccessible.cpp22
-rw-r--r--accessible/windows/msaa/MsaaAccessible.h6
-rw-r--r--accessible/windows/msaa/MsaaIdGenerator.cpp2
-rw-r--r--accessible/windows/msaa/Platform.cpp6
-rw-r--r--accessible/windows/msaa/ServiceProvider.cpp19
-rw-r--r--accessible/windows/msaa/moz.build2
-rw-r--r--accessible/windows/msaa/nsWinUtils.cpp1
-rw-r--r--accessible/windows/sdn/moz.build1
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.cpp132
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.h21
-rw-r--r--accessible/xpcom/moz.build4
-rw-r--r--accessible/xul/moz.build4
175 files changed, 3497 insertions, 802 deletions
diff --git a/accessible/android/AccessibleWrap.cpp b/accessible/android/AccessibleWrap.cpp
index 4bccc2dddd..4ca06dea88 100644
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -356,7 +356,7 @@ void AccessibleWrap::SetVirtualViewID(Accessible* aAccessible,
int32_t AccessibleWrap::GetAndroidClass(role aRole) {
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
return androidClass;
diff --git a/accessible/android/Platform.cpp b/accessible/android/Platform.cpp
index 02f808f8bc..312dbc9636 100644
--- a/accessible/android/Platform.cpp
+++ b/accessible/android/Platform.cpp
@@ -65,7 +65,7 @@ void a11y::PlatformInit() {
// Preload any roles that have localized versions
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
rv = stringBundle->GetStringFromName(stringRole, localizedStr); \
if (NS_SUCCEEDED(rv)) { \
sLocalizedStrings.InsertOrUpdate(u##stringRole##_ns, localizedStr); \
diff --git a/accessible/aom/moz.build b/accessible/aom/moz.build
index 88b941435e..1b1bbc4290 100644
--- a/accessible/aom/moz.build
+++ b/accessible/aom/moz.build
@@ -34,6 +34,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/atk/AccessibleWrap.cpp b/accessible/atk/AccessibleWrap.cpp
index 98f303ee4a..522434561f 100644
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -582,7 +582,7 @@ AtkRole getRoleCB(AtkObject* aAtkObj) {
#endif
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
aAtkObj->role = atkRole; \
break;
diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp
index 01cc5d0417..bfc41db82e 100644
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -733,7 +733,7 @@ static const nsRoleMapEntry sWAIRoleMaps[] = {
},
{ // grid
nsGkAtoms::grid,
- roles::TABLE,
+ roles::GRID,
kUseMapRole,
eNoValue,
eNoAction,
@@ -1480,7 +1480,8 @@ const nsRoleMapEntry* aria::GetRoleMap(dom::Element* aEl) {
return GetRoleMapFromIndex(GetRoleMapIndex(aEl));
}
-uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
+uint8_t aria::GetFirstValidRoleMapIndexExcluding(
+ dom::Element* aEl, std::initializer_list<nsStaticAtom*> aRolesToSkip) {
nsAutoString roles;
if (!aEl || !nsAccUtils::GetARIAAttr(aEl, nsGkAtoms::role, roles) ||
roles.IsEmpty()) {
@@ -1492,6 +1493,19 @@ uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
while (tokenizer.hasMoreTokens()) {
// Do a binary search through table for the next role in role list
const nsDependentSubstring role = tokenizer.nextToken();
+
+ // Skip any roles that we aren't interested in.
+ bool shouldSkip = false;
+ for (nsStaticAtom* atomRole : aRolesToSkip) {
+ if (role.Equals(atomRole->GetUTF16String())) {
+ shouldSkip = true;
+ break;
+ }
+ }
+ if (shouldSkip) {
+ continue;
+ }
+
size_t idx;
auto comparator = [&role](const nsRoleMapEntry& aEntry) {
return Compare(role, aEntry.ARIARoleString(),
@@ -1508,6 +1522,11 @@ uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
return LANDMARK_ROLE_MAP_ENTRY_INDEX;
}
+uint8_t aria::GetRoleMapIndex(dom::Element* aEl) {
+ // Get the rolemap index of the first valid role, excluding nothing.
+ return GetFirstValidRoleMapIndexExcluding(aEl, {});
+}
+
const nsRoleMapEntry* aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) {
switch (aRoleMapIndex) {
case NO_ROLE_MAP_ENTRY_INDEX:
diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h
index 30cc1f0814..58a96b7112 100644
--- a/accessible/base/ARIAMap.h
+++ b/accessible/base/ARIAMap.h
@@ -234,6 +234,19 @@ const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX;
*/
const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
+/*
+ * Get the role map entry pointer's index for a given DOM node, skipping any
+ * given roles. This will use the first valid ARIA role if the role attribute
+ * provides a space delimited list of roles, excluding any given roles.
+ *
+ * @param aEl [in] the DOM node to get the role map entry for
+ * @param aRolesToSkip [in] the roles to skip when searching the role string
+ * @return the index of the pointer to the role map entry for the
+ * ARIA role, or NO_ROLE_MAP_ENTRY_INDEX if none
+ */
+uint8_t GetFirstValidRoleMapIndexExcluding(
+ dom::Element* aEl, std::initializer_list<nsStaticAtom*> aRolesToSkip);
+
/**
* Get the role map entry pointer's index for a given DOM node. This will use
* the first ARIA role if the role attribute provides a space delimited list of
diff --git a/accessible/base/AccGroupInfo.cpp b/accessible/base/AccGroupInfo.cpp
index 3b536b1aa4..c3501dc36e 100644
--- a/accessible/base/AccGroupInfo.cpp
+++ b/accessible/base/AccGroupInfo.cpp
@@ -263,6 +263,7 @@ uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer,
bool* aIsHierarchical) {
uint32_t itemCount = 0;
switch (aContainer->Role()) {
+ case roles::GRID:
case roles::TABLE:
if (auto val = aContainer->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) {
if (*val >= 0) {
diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp
index badd34c0d5..d28d5fcbe9 100644
--- a/accessible/base/AccIterator.cpp
+++ b/accessible/base/AccIterator.cpp
@@ -71,7 +71,12 @@ AccIterator::IteratorState::IteratorState(const LocalAccessible* aParent,
RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
nsIContent* aDependentContent,
nsAtom* aRelAttr)
- : mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0) {
+ : mDocument(aDocument),
+ mDependentContent(aDependentContent),
+ mRelAttr(aRelAttr),
+ mProviders(nullptr),
+ mIndex(0),
+ mIsWalkingDependentElements(false) {
nsAutoString id;
if (aDependentContent->IsElement() &&
aDependentContent->AsElement()->GetAttr(nsGkAtoms::id, id)) {
@@ -80,26 +85,57 @@ RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
}
LocalAccessible* RelatedAccIterator::Next() {
- if (!mProviders) return nullptr;
+ if (!mProviders || mIndex == mProviders->Length()) {
+ if (mIsWalkingDependentElements) {
+ // We've walked both dependent ids and dependent elements, so there are
+ // no more targets.
+ return nullptr;
+ }
+ // We've returned all dependent ids, but there might be dependent elements
+ // too. Walk those next.
+ mIsWalkingDependentElements = true;
+ mIndex = 0;
+ if (auto providers =
+ mDocument->mDependentElementsMap.Lookup(mDependentContent)) {
+ mProviders = &providers.Data();
+ } else {
+ mProviders = nullptr;
+ return nullptr;
+ }
+ }
while (mIndex < mProviders->Length()) {
const auto& provider = (*mProviders)[mIndex++];
// Return related accessible for the given attribute.
- if (provider->mRelAttr == mRelAttr) {
- LocalAccessible* related = mDocument->GetAccessible(provider->mContent);
- if (related) {
- return related;
- }
+ if (mRelAttr && provider->mRelAttr != mRelAttr) {
+ continue;
+ }
+ // If we're walking elements (not ids), the explicitly set attr-element
+ // `mDependentContent` must be a descendant of any of the refering element
+ // `mProvider->mContent`'s shadow-including ancestors.
+ if (mIsWalkingDependentElements &&
+ !nsCoreUtils::IsDescendantOfAnyShadowIncludingAncestor(
+ mDependentContent, provider->mContent)) {
+ continue;
+ }
+ LocalAccessible* related = mDocument->GetAccessible(provider->mContent);
+ if (related) {
+ return related;
+ }
- // If the document content is pointed by relation then return the
- // document itself.
- if (provider->mContent == mDocument->GetContent()) {
- return mDocument;
- }
+ // If the document content is pointed by relation then return the
+ // document itself.
+ if (provider->mContent == mDocument->GetContent()) {
+ return mDocument;
}
}
+ // We exhausted mProviders without returning anything.
+ if (!mIsWalkingDependentElements) {
+ // Call this function again to start walking the dependent elements.
+ return Next();
+ }
return nullptr;
}
diff --git a/accessible/base/AccIterator.h b/accessible/base/AccIterator.h
index 463e3e9d3e..61b126c812 100644
--- a/accessible/base/AccIterator.h
+++ b/accessible/base/AccIterator.h
@@ -67,7 +67,9 @@ class AccIterator : public AccIterable {
/**
* Allows to traverse through related accessibles that are pointing to the given
- * dependent accessible by relation attribute.
+ * dependent accessible by relation attribute. This is typically used to query
+ * implicit reverse relations; e.g. calculating the LABEL_FOR relation for a
+ * label where that label was referenced using aria-labelledby.
*/
class RelatedAccIterator : public AccIterable {
public:
@@ -79,7 +81,7 @@ class RelatedAccIterator : public AccIterable {
* @param aDependentContent [in] the content of dependent accessible that
* relations were requested for
* @param aRelAttr [in] relation attribute that relations are
- * pointed by
+ * pointed by, null for all relations
*/
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
nsAtom* aRelAttr);
@@ -97,9 +99,11 @@ class RelatedAccIterator : public AccIterable {
RelatedAccIterator& operator=(const RelatedAccIterator&);
DocAccessible* mDocument;
+ nsIContent* mDependentContent;
nsAtom* mRelAttr;
DocAccessible::AttrRelProviders* mProviders;
uint32_t mIndex;
+ bool mIsWalkingDependentElements;
};
/**
diff --git a/accessible/base/Asserts.cpp b/accessible/base/Asserts.cpp
index efdd733d9b..729b4e1ea5 100644
--- a/accessible/base/Asserts.cpp
+++ b/accessible/base/Asserts.cpp
@@ -12,7 +12,7 @@
using namespace mozilla::a11y;
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
static_assert( \
static_cast<uint32_t>(roles::geckoRole) == \
static_cast<uint32_t>(nsIAccessibleRole::ROLE_##geckoRole), \
diff --git a/accessible/base/HTMLMarkupMap.h b/accessible/base/HTMLMarkupMap.h
index b903097ea0..c607616518 100644
--- a/accessible/base/HTMLMarkupMap.h
+++ b/accessible/base/HTMLMarkupMap.h
@@ -34,7 +34,12 @@ MARKUPMAP(address, New_HyperText, roles::GROUPING)
MARKUPMAP(article, New_HyperText, roles::ARTICLE, Attr(xmlroles, article))
-MARKUPMAP(aside, New_HyperText, roles::LANDMARK)
+MARKUPMAP(
+ aside,
+ [](Element* aElement, LocalAccessible* aContext) -> LocalAccessible* {
+ return new HTMLAsideAccessible(aElement, aContext->Document());
+ },
+ 0)
MARKUPMAP(blockquote, New_HyperText, roles::BLOCKQUOTE)
diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h
index 23f214246f..68529d176a 100644
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -47,10 +47,10 @@ EPlatformDisabledState PlatformDisabledState();
void PreInit();
#endif
-#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_MACOSX)
+#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_DARWIN)
/**
* Is platform accessibility enabled.
- * Only used on linux with atk and MacOS for now.
+ * Only used on Linux, MacOS and iOS for now.
*/
bool ShouldA11yBeEnabled();
#endif
diff --git a/accessible/base/RoleMap.h b/accessible/base/RoleMap.h
index ce82000188..58cbb82165 100644
--- a/accessible/base/RoleMap.h
+++ b/accessible/base/RoleMap.h
@@ -17,6 +17,7 @@ ROLE(NOTHING,
ROLE_SYSTEM_CLIENT,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::No,
eNameFromSubtreeIfReqRule)
ROLE(MENUBAR,
@@ -28,6 +29,7 @@ ROLE(MENUBAR,
ROLE_SYSTEM_MENUBAR,
ROLE_SYSTEM_MENUBAR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(SCROLLBAR,
@@ -39,6 +41,7 @@ ROLE(SCROLLBAR,
ROLE_SYSTEM_SCROLLBAR,
ROLE_SYSTEM_SCROLLBAR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromValueRule)
ROLE(ALERT,
@@ -50,6 +53,7 @@ ROLE(ALERT,
ROLE_SYSTEM_ALERT,
ROLE_SYSTEM_ALERT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(INTERNAL_FRAME,
@@ -61,6 +65,7 @@ ROLE(INTERNAL_FRAME,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_INTERNAL_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MENUPOPUP,
@@ -72,6 +77,7 @@ ROLE(MENUPOPUP,
ROLE_SYSTEM_MENUPOPUP,
ROLE_SYSTEM_MENUPOPUP,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MENUITEM,
@@ -83,6 +89,7 @@ ROLE(MENUITEM,
ROLE_SYSTEM_MENUITEM,
ROLE_SYSTEM_MENUITEM,
java::SessionAccessibility::CLASSNAME_MENUITEM,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(TOOLTIP,
@@ -94,6 +101,7 @@ ROLE(TOOLTIP,
ROLE_SYSTEM_TOOLTIP,
ROLE_SYSTEM_TOOLTIP,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(APPLICATION,
@@ -105,6 +113,7 @@ ROLE(APPLICATION,
ROLE_SYSTEM_APPLICATION,
ROLE_SYSTEM_APPLICATION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(DOCUMENT,
@@ -116,6 +125,7 @@ ROLE(DOCUMENT,
ROLE_SYSTEM_DOCUMENT,
ROLE_SYSTEM_DOCUMENT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
/**
@@ -136,6 +146,7 @@ ROLE(PANE,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(DIALOG,
@@ -147,6 +158,7 @@ ROLE(DIALOG,
ROLE_SYSTEM_DIALOG,
ROLE_SYSTEM_DIALOG,
java::SessionAccessibility::CLASSNAME_DIALOG,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(GROUPING,
@@ -158,6 +170,7 @@ ROLE(GROUPING,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(SEPARATOR,
@@ -169,6 +182,7 @@ ROLE(SEPARATOR,
ROLE_SYSTEM_SEPARATOR,
ROLE_SYSTEM_SEPARATOR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNoNameRule)
ROLE(TOOLBAR,
@@ -180,6 +194,7 @@ ROLE(TOOLBAR,
ROLE_SYSTEM_TOOLBAR,
ROLE_SYSTEM_TOOLBAR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(STATUSBAR,
@@ -191,6 +206,7 @@ ROLE(STATUSBAR,
ROLE_SYSTEM_STATUSBAR,
ROLE_SYSTEM_STATUSBAR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(TABLE,
@@ -202,6 +218,7 @@ ROLE(TABLE,
ROLE_SYSTEM_TABLE,
ROLE_SYSTEM_TABLE,
java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(COLUMNHEADER,
@@ -213,6 +230,7 @@ ROLE(COLUMNHEADER,
ROLE_SYSTEM_COLUMNHEADER,
ROLE_SYSTEM_COLUMNHEADER,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(ROWHEADER,
@@ -224,6 +242,7 @@ ROLE(ROWHEADER,
ROLE_SYSTEM_ROWHEADER,
ROLE_SYSTEM_ROWHEADER,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(ROW,
@@ -235,6 +254,7 @@ ROLE(ROW,
ROLE_SYSTEM_ROW,
ROLE_SYSTEM_ROW,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(CELL,
@@ -246,6 +266,7 @@ ROLE(CELL,
ROLE_SYSTEM_CELL,
ROLE_SYSTEM_CELL,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(LINK,
@@ -257,6 +278,7 @@ ROLE(LINK,
ROLE_SYSTEM_LINK,
ROLE_SYSTEM_LINK,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfBrokenUp,
eNameFromSubtreeRule)
ROLE(LIST,
@@ -268,6 +290,7 @@ ROLE(LIST,
ROLE_SYSTEM_LIST,
ROLE_SYSTEM_LIST,
java::SessionAccessibility::CLASSNAME_LISTVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(LISTITEM,
@@ -279,6 +302,7 @@ ROLE(LISTITEM,
ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_LISTITEM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(OUTLINE,
@@ -290,6 +314,7 @@ ROLE(OUTLINE,
ROLE_SYSTEM_OUTLINE,
ROLE_SYSTEM_OUTLINE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(OUTLINEITEM,
@@ -301,6 +326,7 @@ ROLE(OUTLINEITEM,
ROLE_SYSTEM_OUTLINEITEM,
ROLE_SYSTEM_OUTLINEITEM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(PAGETAB,
@@ -312,6 +338,7 @@ ROLE(PAGETAB,
ROLE_SYSTEM_PAGETAB,
ROLE_SYSTEM_PAGETAB,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(PROPERTYPAGE,
@@ -323,6 +350,7 @@ ROLE(PROPERTYPAGE,
ROLE_SYSTEM_PROPERTYPAGE,
ROLE_SYSTEM_PROPERTYPAGE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(GRAPHIC,
@@ -334,6 +362,7 @@ ROLE(GRAPHIC,
ROLE_SYSTEM_GRAPHIC,
ROLE_SYSTEM_GRAPHIC,
java::SessionAccessibility::CLASSNAME_IMAGE,
+ IsAccessibilityElementRule::Yes,
eNoNameRule)
ROLE(STATICTEXT,
@@ -345,6 +374,7 @@ ROLE(STATICTEXT,
ROLE_SYSTEM_STATICTEXT,
ROLE_SYSTEM_STATICTEXT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfParentIsntElementWithName,
eNoNameRule)
ROLE(TEXT_LEAF,
@@ -356,6 +386,7 @@ ROLE(TEXT_LEAF,
ROLE_SYSTEM_TEXT,
ROLE_SYSTEM_TEXT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfParentIsntElementWithName,
eNoNameRule)
ROLE(PUSHBUTTON,
@@ -367,6 +398,7 @@ ROLE(PUSHBUTTON,
ROLE_SYSTEM_PUSHBUTTON,
ROLE_SYSTEM_PUSHBUTTON,
java::SessionAccessibility::CLASSNAME_BUTTON,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(CHECKBUTTON,
@@ -378,6 +410,7 @@ ROLE(CHECKBUTTON,
ROLE_SYSTEM_CHECKBUTTON,
ROLE_SYSTEM_CHECKBUTTON,
java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(RADIOBUTTON,
@@ -389,6 +422,7 @@ ROLE(RADIOBUTTON,
ROLE_SYSTEM_RADIOBUTTON,
ROLE_SYSTEM_RADIOBUTTON,
java::SessionAccessibility::CLASSNAME_RADIOBUTTON,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
// Equivalent of HTML select element with size="1". See also EDITCOMBOBOX.
@@ -401,6 +435,7 @@ ROLE(COMBOBOX,
ROLE_SYSTEM_COMBOBOX,
ROLE_SYSTEM_COMBOBOX,
java::SessionAccessibility::CLASSNAME_SPINNER,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(PROGRESSBAR,
@@ -412,6 +447,7 @@ ROLE(PROGRESSBAR,
ROLE_SYSTEM_PROGRESSBAR,
ROLE_SYSTEM_PROGRESSBAR,
java::SessionAccessibility::CLASSNAME_PROGRESSBAR,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(SLIDER,
@@ -423,6 +459,7 @@ ROLE(SLIDER,
ROLE_SYSTEM_SLIDER,
ROLE_SYSTEM_SLIDER,
java::SessionAccessibility::CLASSNAME_SEEKBAR,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(SPINBUTTON,
@@ -434,6 +471,7 @@ ROLE(SPINBUTTON,
ROLE_SYSTEM_SPINBUTTON,
ROLE_SYSTEM_SPINBUTTON,
java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(DIAGRAM,
@@ -445,6 +483,7 @@ ROLE(DIAGRAM,
ROLE_SYSTEM_DIAGRAM,
ROLE_SYSTEM_DIAGRAM,
java::SessionAccessibility::CLASSNAME_IMAGE,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(ANIMATION,
@@ -456,6 +495,7 @@ ROLE(ANIMATION,
ROLE_SYSTEM_ANIMATION,
ROLE_SYSTEM_ANIMATION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(BUTTONDROPDOWN,
@@ -467,6 +507,7 @@ ROLE(BUTTONDROPDOWN,
ROLE_SYSTEM_BUTTONDROPDOWN,
ROLE_SYSTEM_BUTTONDROPDOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(BUTTONMENU,
@@ -478,6 +519,7 @@ ROLE(BUTTONMENU,
ROLE_SYSTEM_BUTTONMENU,
ROLE_SYSTEM_BUTTONMENU,
java::SessionAccessibility::CLASSNAME_SPINNER,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(WHITESPACE,
@@ -489,6 +531,7 @@ ROLE(WHITESPACE,
ROLE_SYSTEM_WHITESPACE,
ROLE_SYSTEM_WHITESPACE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::No,
eNoNameRule)
ROLE(PAGETABLIST,
@@ -500,6 +543,7 @@ ROLE(PAGETABLIST,
ROLE_SYSTEM_PAGETABLIST,
ROLE_SYSTEM_PAGETABLIST,
java::SessionAccessibility::CLASSNAME_TABWIDGET,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CANVAS,
@@ -511,6 +555,7 @@ ROLE(CANVAS,
ROLE_SYSTEM_GRAPHIC,
IA2_ROLE_CANVAS,
java::SessionAccessibility::CLASSNAME_IMAGE,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CHECK_MENU_ITEM,
@@ -522,6 +567,7 @@ ROLE(CHECK_MENU_ITEM,
ROLE_SYSTEM_MENUITEM,
IA2_ROLE_CHECK_MENU_ITEM,
java::SessionAccessibility::CLASSNAME_MENUITEM,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(DATE_EDITOR,
@@ -533,6 +579,7 @@ ROLE(DATE_EDITOR,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_DATE_EDITOR,
java::SessionAccessibility::CLASSNAME_SPINNER,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CHROME_WINDOW,
@@ -544,6 +591,7 @@ ROLE(CHROME_WINDOW,
ROLE_SYSTEM_APPLICATION,
IA2_ROLE_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(LABEL,
@@ -555,6 +603,7 @@ ROLE(LABEL,
ROLE_SYSTEM_STATICTEXT,
IA2_ROLE_LABEL,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(PASSWORD_TEXT,
@@ -566,6 +615,7 @@ ROLE(PASSWORD_TEXT,
ROLE_SYSTEM_TEXT,
ROLE_SYSTEM_TEXT,
java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ IsAccessibilityElementRule::Yes,
eNoNameRule)
ROLE(RADIO_MENU_ITEM,
@@ -577,6 +627,7 @@ ROLE(RADIO_MENU_ITEM,
ROLE_SYSTEM_MENUITEM,
IA2_ROLE_RADIO_MENU_ITEM,
java::SessionAccessibility::CLASSNAME_MENUITEM,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(TEXT_CONTAINER,
@@ -588,6 +639,7 @@ ROLE(TEXT_CONTAINER,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(TOGGLE_BUTTON,
@@ -599,6 +651,7 @@ ROLE(TOGGLE_BUTTON,
ROLE_SYSTEM_PUSHBUTTON,
IA2_ROLE_TOGGLE_BUTTON,
java::SessionAccessibility::CLASSNAME_TOGGLEBUTTON,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(TREE_TABLE,
@@ -610,6 +663,7 @@ ROLE(TREE_TABLE,
ROLE_SYSTEM_OUTLINE,
ROLE_SYSTEM_OUTLINE,
java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(PARAGRAPH,
@@ -621,6 +675,7 @@ ROLE(PARAGRAPH,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_PARAGRAPH,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(ENTRY,
@@ -632,6 +687,7 @@ ROLE(ENTRY,
ROLE_SYSTEM_TEXT,
ROLE_SYSTEM_TEXT,
java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(CAPTION,
@@ -643,6 +699,7 @@ ROLE(CAPTION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_CAPTION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(NON_NATIVE_DOCUMENT,
@@ -654,6 +711,7 @@ ROLE(NON_NATIVE_DOCUMENT,
ROLE_SYSTEM_DOCUMENT,
ROLE_SYSTEM_DOCUMENT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(HEADING,
@@ -665,6 +723,7 @@ ROLE(HEADING,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_HEADING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildless,
eNameFromSubtreeRule)
ROLE(SECTION,
@@ -676,6 +735,7 @@ ROLE(SECTION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_SECTION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(FORM,
@@ -687,6 +747,7 @@ ROLE(FORM,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_FORM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(APP_ROOT,
@@ -698,6 +759,7 @@ ROLE(APP_ROOT,
ROLE_SYSTEM_APPLICATION,
ROLE_SYSTEM_APPLICATION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(PARENT_MENUITEM,
@@ -709,6 +771,7 @@ ROLE(PARENT_MENUITEM,
ROLE_SYSTEM_MENUITEM,
ROLE_SYSTEM_MENUITEM,
java::SessionAccessibility::CLASSNAME_MENUITEM,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(COMBOBOX_LIST,
@@ -720,6 +783,7 @@ ROLE(COMBOBOX_LIST,
ROLE_SYSTEM_LIST,
ROLE_SYSTEM_LIST,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::No,
eNoNameRule)
ROLE(COMBOBOX_OPTION,
@@ -731,6 +795,7 @@ ROLE(COMBOBOX_OPTION,
ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_LISTITEM,
java::SessionAccessibility::CLASSNAME_MENUITEM,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(IMAGE_MAP,
@@ -742,6 +807,7 @@ ROLE(IMAGE_MAP,
ROLE_SYSTEM_GRAPHIC,
ROLE_SYSTEM_GRAPHIC,
java::SessionAccessibility::CLASSNAME_IMAGE,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(OPTION,
@@ -753,6 +819,7 @@ ROLE(OPTION,
ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_LISTITEM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(RICH_OPTION,
@@ -764,6 +831,7 @@ ROLE(RICH_OPTION,
ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_LISTITEM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(LISTBOX,
@@ -775,7 +843,8 @@ ROLE(LISTBOX,
ROLE_SYSTEM_LIST,
ROLE_SYSTEM_LIST,
java::SessionAccessibility::CLASSNAME_LISTVIEW,
- eNoNameRule)
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
+ eNameFromValueRule)
ROLE(FLAT_EQUATION,
"flat equation",
@@ -786,6 +855,7 @@ ROLE(FLAT_EQUATION,
ROLE_SYSTEM_EQUATION,
ROLE_SYSTEM_EQUATION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNoNameRule)
ROLE(GRID_CELL,
@@ -797,6 +867,7 @@ ROLE(GRID_CELL,
ROLE_SYSTEM_CELL,
ROLE_SYSTEM_CELL,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(NOTE,
@@ -808,6 +879,7 @@ ROLE(NOTE,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_NOTE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(FIGURE,
@@ -819,6 +891,7 @@ ROLE(FIGURE,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CHECK_RICH_OPTION,
@@ -830,6 +903,7 @@ ROLE(CHECK_RICH_OPTION,
ROLE_SYSTEM_CHECKBUTTON,
ROLE_SYSTEM_CHECKBUTTON,
java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(DEFINITION_LIST,
@@ -841,6 +915,7 @@ ROLE(DEFINITION_LIST,
ROLE_SYSTEM_LIST,
ROLE_SYSTEM_LIST,
java::SessionAccessibility::CLASSNAME_LISTVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(TERM,
@@ -852,6 +927,7 @@ ROLE(TERM,
ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_LISTITEM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(DEFINITION,
@@ -863,6 +939,7 @@ ROLE(DEFINITION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_PARAGRAPH,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(KEY,
@@ -874,6 +951,7 @@ ROLE(KEY,
ROLE_SYSTEM_PUSHBUTTON,
ROLE_SYSTEM_PUSHBUTTON,
java::SessionAccessibility::CLASSNAME_BUTTON,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(SWITCH,
@@ -885,6 +963,7 @@ ROLE(SWITCH,
ROLE_SYSTEM_CHECKBUTTON,
IA2_ROLE_TOGGLE_BUTTON,
java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(MATHML_MATH,
@@ -896,6 +975,7 @@ ROLE(MATHML_MATH,
ROLE_SYSTEM_EQUATION,
ROLE_SYSTEM_EQUATION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_IDENTIFIER,
@@ -907,6 +987,7 @@ ROLE(MATHML_IDENTIFIER,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_NUMBER,
@@ -918,6 +999,7 @@ ROLE(MATHML_NUMBER,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_OPERATOR,
@@ -934,6 +1016,7 @@ ROLE(MATHML_OPERATOR,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_TEXT,
@@ -945,6 +1028,7 @@ ROLE(MATHML_TEXT,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_STRING_LITERAL,
@@ -956,6 +1040,7 @@ ROLE(MATHML_STRING_LITERAL,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_GLYPH,
@@ -967,6 +1052,7 @@ ROLE(MATHML_GLYPH,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_IMAGE,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeRule)
ROLE(MATHML_ROW,
@@ -978,6 +1064,7 @@ ROLE(MATHML_ROW,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_FRACTION,
@@ -989,6 +1076,7 @@ ROLE(MATHML_FRACTION,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_SQUARE_ROOT,
@@ -1000,6 +1088,7 @@ ROLE(MATHML_SQUARE_ROOT,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_ROOT,
@@ -1011,6 +1100,7 @@ ROLE(MATHML_ROOT,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_ENCLOSED,
@@ -1022,6 +1112,7 @@ ROLE(MATHML_ENCLOSED,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STYLE,
@@ -1033,6 +1124,7 @@ ROLE(MATHML_STYLE,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_SUB,
@@ -1044,6 +1136,7 @@ ROLE(MATHML_SUB,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_SUP,
@@ -1055,6 +1148,7 @@ ROLE(MATHML_SUP,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_SUB_SUP,
@@ -1066,6 +1160,7 @@ ROLE(MATHML_SUB_SUP,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_UNDER,
@@ -1077,6 +1172,7 @@ ROLE(MATHML_UNDER,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_OVER,
@@ -1088,6 +1184,7 @@ ROLE(MATHML_OVER,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_UNDER_OVER,
@@ -1099,6 +1196,7 @@ ROLE(MATHML_UNDER_OVER,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_MULTISCRIPTS,
@@ -1110,6 +1208,7 @@ ROLE(MATHML_MULTISCRIPTS,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_TABLE,
@@ -1121,6 +1220,7 @@ ROLE(MATHML_TABLE,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_LABELED_ROW,
@@ -1132,6 +1232,7 @@ ROLE(MATHML_LABELED_ROW,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_TABLE_ROW,
@@ -1143,6 +1244,7 @@ ROLE(MATHML_TABLE_ROW,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_CELL,
@@ -1154,6 +1256,7 @@ ROLE(MATHML_CELL,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_ACTION,
@@ -1165,6 +1268,7 @@ ROLE(MATHML_ACTION,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_ERROR,
@@ -1176,6 +1280,7 @@ ROLE(MATHML_ERROR,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK,
@@ -1187,6 +1292,7 @@ ROLE(MATHML_STACK,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_LONG_DIVISION,
@@ -1198,6 +1304,7 @@ ROLE(MATHML_LONG_DIVISION,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK_GROUP,
@@ -1209,6 +1316,7 @@ ROLE(MATHML_STACK_GROUP,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK_ROW,
@@ -1220,6 +1328,7 @@ ROLE(MATHML_STACK_ROW,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK_CARRIES,
@@ -1231,6 +1340,7 @@ ROLE(MATHML_STACK_CARRIES,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK_CARRY,
@@ -1242,6 +1352,7 @@ ROLE(MATHML_STACK_CARRY,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MATHML_STACK_LINE,
@@ -1253,6 +1364,7 @@ ROLE(MATHML_STACK_LINE,
0,
IA2_ROLE_UNKNOWN,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(RADIO_GROUP,
@@ -1264,6 +1376,7 @@ ROLE(RADIO_GROUP,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(TEXT,
@@ -1275,6 +1388,7 @@ ROLE(TEXT,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(DETAILS,
@@ -1286,6 +1400,7 @@ ROLE(DETAILS,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(SUMMARY,
@@ -1297,6 +1412,7 @@ ROLE(SUMMARY,
ROLE_SYSTEM_PUSHBUTTON,
ROLE_SYSTEM_PUSHBUTTON,
java::SessionAccessibility::CLASSNAME_BUTTON,
+ IsAccessibilityElementRule::Yes,
eNameFromSubtreeRule)
ROLE(LANDMARK,
@@ -1308,6 +1424,7 @@ ROLE(LANDMARK,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_LANDMARK,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(NAVIGATION,
@@ -1319,6 +1436,7 @@ ROLE(NAVIGATION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_LANDMARK,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(FOOTNOTE,
@@ -1330,6 +1448,7 @@ ROLE(FOOTNOTE,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_FOOTNOTE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(ARTICLE,
@@ -1341,6 +1460,7 @@ ROLE(ARTICLE,
ROLE_SYSTEM_DOCUMENT,
ROLE_SYSTEM_DOCUMENT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(REGION,
@@ -1352,6 +1472,7 @@ ROLE(REGION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_LANDMARK,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
// A composite widget with a text input and popup. Used for ARIA role combobox.
@@ -1365,6 +1486,7 @@ ROLE(EDITCOMBOBOX,
ROLE_SYSTEM_COMBOBOX,
ROLE_SYSTEM_COMBOBOX,
java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(BLOCKQUOTE,
@@ -1376,6 +1498,7 @@ ROLE(BLOCKQUOTE,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_BLOCK_QUOTE,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CONTENT_DELETION,
@@ -1387,6 +1510,7 @@ ROLE(CONTENT_DELETION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_CONTENT_DELETION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(CONTENT_INSERTION,
@@ -1398,6 +1522,7 @@ ROLE(CONTENT_INSERTION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_CONTENT_INSERTION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(FORM_LANDMARK,
@@ -1409,6 +1534,7 @@ ROLE(FORM_LANDMARK,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_FORM,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(MARK,
@@ -1420,6 +1546,7 @@ ROLE(MARK,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_MARK,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(SUGGESTION,
@@ -1431,6 +1558,7 @@ ROLE(SUGGESTION,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_SUGGESTION,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(COMMENT,
@@ -1442,6 +1570,7 @@ ROLE(COMMENT,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_COMMENT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(CODE,
@@ -1453,6 +1582,7 @@ ROLE(CODE,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(TIME_EDITOR,
@@ -1464,6 +1594,7 @@ ROLE(TIME_EDITOR,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(LISTITEM_MARKER,
@@ -1475,6 +1606,7 @@ ROLE(LISTITEM_MARKER,
ROLE_SYSTEM_STATICTEXT,
ROLE_SYSTEM_STATICTEXT,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNoNameRule)
ROLE(METER,
@@ -1486,6 +1618,7 @@ ROLE(METER,
ROLE_SYSTEM_PROGRESSBAR,
ROLE_SYSTEM_PROGRESSBAR,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::Yes,
eNameFromValueRule)
ROLE(SUBSCRIPT,
@@ -1497,6 +1630,7 @@ ROLE(SUBSCRIPT,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(SUPERSCRIPT,
@@ -1508,6 +1642,7 @@ ROLE(SUPERSCRIPT,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(EMPHASIS,
@@ -1519,6 +1654,7 @@ ROLE(EMPHASIS,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(STRONG,
@@ -1530,6 +1666,7 @@ ROLE(STRONG,
ROLE_SYSTEM_GROUPING,
IA2_ROLE_TEXT_FRAME,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
ROLE(TIME,
@@ -1541,6 +1678,18 @@ ROLE(TIME,
ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_GROUPING,
java::SessionAccessibility::CLASSNAME_VIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
eNameFromSubtreeIfReqRule)
+ROLE(GRID,
+ "grid",
+ nsGkAtoms::grid,
+ ATK_ROLE_TABLE,
+ NSAccessibilityTableRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TABLE,
+ ROLE_SYSTEM_TABLE,
+ java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ IsAccessibilityElementRule::IfChildlessWithNameAndFocusable,
+ eNameFromSubtreeIfReqRule)
// clang-format on
diff --git a/accessible/base/moz.build b/accessible/base/moz.build
index b65c90ceba..de8e75f0e1 100644
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -112,6 +112,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
index c31dd666ce..d38276572b 100644
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -606,6 +606,24 @@ void nsAccessibilityService::NotifyOfDevPixelRatioChange(
}
}
+void nsAccessibilityService::NotifyAttrElementWillChange(
+ mozilla::dom::Element* aElement, nsAtom* aAttr) {
+ mozilla::dom::Document* doc = aElement->OwnerDoc();
+ MOZ_ASSERT(doc);
+ if (DocAccessible* docAcc = GetDocAccessible(doc)) {
+ docAcc->AttrElementWillChange(aElement, aAttr);
+ }
+}
+
+void nsAccessibilityService::NotifyAttrElementChanged(
+ mozilla::dom::Element* aElement, nsAtom* aAttr) {
+ mozilla::dom::Document* doc = aElement->OwnerDoc();
+ MOZ_ASSERT(doc);
+ if (DocAccessible* docAcc = GetDocAccessible(doc)) {
+ docAcc->AttrElementChanged(aElement, aAttr);
+ }
+}
+
LocalAccessible* nsAccessibilityService::GetRootDocumentAccessible(
PresShell* aPresShell, bool aCanCreate) {
PresShell* presShell = aPresShell;
@@ -820,7 +838,7 @@ void nsAccessibilityService::RecreateAccessible(PresShell* aPresShell,
void nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) {
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
aString.AssignLiteral(stringRole); \
return;
@@ -1504,8 +1522,8 @@ bool nsAccessibilityService::Init() {
NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
gApplicationAccessible->Init();
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Accessibility,
- "Active"_ns);
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::Accessibility, "Active");
// Now its safe to start platform accessibility.
if (XRE_IsParentProcess()) PlatformInit();
diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h
index 0b3f172f89..77b56f2fc1 100644
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -244,6 +244,19 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
void NotifyOfDevPixelRatioChange(mozilla::PresShell* aPresShell,
int32_t aAppUnitsPerDevPixel);
+ /**
+ * Notify accessibility that an element explicitly set for an attribute is
+ * about to change. See dom::Element::ExplicitlySetAttrElement.
+ */
+ void NotifyAttrElementWillChange(mozilla::dom::Element* aElement,
+ nsAtom* aAttr);
+
+ /**
+ * Notify accessibility that an element explicitly set for an attribute has
+ * changed. See dom::Element::ExplicitlySetAttrElement.
+ */
+ void NotifyAttrElementChanged(mozilla::dom::Element* aElement, nsAtom* aAttr);
+
// nsAccessibiltiyService
/**
diff --git a/accessible/base/nsCoreUtils.cpp b/accessible/base/nsCoreUtils.cpp
index 80739bb401..c5e89258fa 100644
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -35,6 +35,7 @@
#include "nsTreeColumns.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInternals.h"
#include "mozilla/dom/HTMLLabelElement.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/Selection.h"
@@ -546,6 +547,32 @@ 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);
@@ -620,3 +647,34 @@ bool nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(
} while ((parent = parent->GetInProcessParentDocument()));
return true;
}
+
+bool nsCoreUtils::IsDescendantOfAnyShadowIncludingAncestor(
+ nsINode* aDescendant, nsINode* aStartAncestor) {
+ const nsINode* descRoot = aDescendant->SubtreeRoot();
+ nsINode* ancRoot = aStartAncestor->SubtreeRoot();
+ for (;;) {
+ if (ancRoot == descRoot) {
+ return true;
+ }
+ auto* shadow = mozilla::dom::ShadowRoot::FromNode(ancRoot);
+ if (!shadow || !shadow->GetHost()) {
+ break;
+ }
+ ancRoot = shadow->GetHost()->SubtreeRoot();
+ }
+ return false;
+}
+
+Element* nsCoreUtils::GetAriaActiveDescendantElement(Element* aElement) {
+ if (Element* activeDescendant = aElement->GetAriaActiveDescendantElement()) {
+ return activeDescendant;
+ }
+
+ if (auto* element = nsGenericHTMLElement::FromNode(aElement)) {
+ if (auto* internals = element->GetInternals()) {
+ return internals->GetAriaActiveDescendantElement();
+ }
+ }
+
+ return nullptr;
+}
diff --git a/accessible/base/nsCoreUtils.h b/accessible/base/nsCoreUtils.h
index 2c3e7330ff..5e77d6bfe0 100644
--- a/accessible/base/nsCoreUtils.h
+++ b/accessible/base/nsCoreUtils.h
@@ -30,6 +30,7 @@ namespace mozilla {
class PresShell;
namespace dom {
class Document;
+class Element;
class XULTreeElement;
} // namespace dom
} // namespace mozilla
@@ -41,6 +42,7 @@ class nsCoreUtils {
public:
typedef mozilla::PresShell PresShell;
typedef mozilla::dom::Document Document;
+ typedef mozilla::dom::Element Element;
/**
* Return true if the given node is a label of a control.
@@ -305,6 +307,11 @@ 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.
*/
@@ -324,6 +331,15 @@ class nsCoreUtils {
*/
static bool IsDocumentVisibleConsideringInProcessAncestors(
const Document* aDocument);
+
+ /**
+ * Return true if `aDescendant` is a descendant of any of `aStartAncestor`'s
+ * shadow-including ancestors.
+ */
+ static bool IsDescendantOfAnyShadowIncludingAncestor(nsINode* aDescendant,
+ nsINode* aStartAncestor);
+
+ static Element* GetAriaActiveDescendantElement(Element* aElement);
};
#endif
diff --git a/accessible/base/nsTextEquivUtils.cpp b/accessible/base/nsTextEquivUtils.cpp
index d95229c1dc..f222930981 100644
--- a/accessible/base/nsTextEquivUtils.cpp
+++ b/accessible/base/nsTextEquivUtils.cpp
@@ -177,6 +177,16 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
bool isEmptyTextEquiv = true;
+ // Attempt to find the value. If it's non-empty, append and return it. See the
+ // "embedded control" section of the name spec.
+ nsAutoString val;
+ nsresult rv = AppendFromValue(aAccessible, &val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (rv == NS_OK) {
+ AppendString(aString, val);
+ return NS_OK;
+ }
+
// If the name is from tooltip then append it to result string in the end
// (see h. step of name computation guide).
nsAutoString text;
@@ -184,12 +194,6 @@ nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
isEmptyTextEquiv = !AppendString(aString, text);
}
- // Implementation of f. step.
- nsresult rv = AppendFromValue(aAccessible, aString);
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
-
// Implementation of g) step of text equivalent computation guide. Go down
// into subtree if accessible allows "text equivalent from subtree rule" or
// it's not root and not control.
@@ -230,6 +234,19 @@ nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
nsAutoString text;
if (aAccessible != sInitiatorAcc) {
+ // For listboxes in non-initiator computations, we need to get the selected
+ // item and append its text alternative.
+ if (aAccessible->IsListControl()) {
+ Accessible* selected = aAccessible->GetSelectedItem(0);
+ if (selected) {
+ nsresult rv = AppendFromAccessible(selected, &text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AppendString(aString, text) ? NS_OK
+ : NS_OK_NO_NAME_CLAUSE_HANDLED;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
aAccessible->Value(text);
return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
@@ -306,7 +323,7 @@ bool nsTextEquivUtils::AppendString(nsAString* aString,
uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
return nameRule;
diff --git a/accessible/basetypes/Accessible.cpp b/accessible/basetypes/Accessible.cpp
index 05c8270483..de9740dd18 100644
--- a/accessible/basetypes/Accessible.cpp
+++ b/accessible/basetypes/Accessible.cpp
@@ -468,7 +468,7 @@ void Accessible::DebugPrint(const char* aPrefix,
} else {
desc.AssignLiteral("[null]");
}
-# if defined(ANDROID)
+# if defined(ANDROID) || defined(MOZ_WIDGET_UIKIT)
printf_stderr("%s %s\n", aPrefix, desc.get());
# else
printf("%s %s\n", aPrefix, desc.get());
@@ -539,17 +539,13 @@ nsStaticAtom* Accessible::LandmarkRole() const {
}
if (tagName == nsGkAtoms::section) {
- nsAutoString name;
- Name(name);
- if (!name.IsEmpty()) {
+ if (!NameIsEmpty()) {
return nsGkAtoms::region;
}
}
if (tagName == nsGkAtoms::form) {
- nsAutoString name;
- Name(name);
- if (!name.IsEmpty()) {
+ if (!NameIsEmpty()) {
return nsGkAtoms::form;
}
}
@@ -567,8 +563,10 @@ nsStaticAtom* Accessible::LandmarkRole() const {
nsStaticAtom* Accessible::ComputedARIARole() const {
const nsRoleMapEntry* roleMap = ARIARoleMap();
if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty &&
- // region has its own Gecko role and it needs to be handled specially.
+ // region and form have their own Gecko roles and need to be handled
+ // specially.
roleMap->roleAtom != nsGkAtoms::region &&
+ roleMap->roleAtom != nsGkAtoms::form &&
(roleMap->roleRule == kUseNativeRole || roleMap->IsOfType(eLandmark) ||
roleMap->roleAtom == nsGkAtoms::alertdialog ||
roleMap->roleAtom == nsGkAtoms::feed ||
@@ -596,7 +594,7 @@ nsStaticAtom* Accessible::ComputedARIARole() const {
}
// Role from native markup or layout.
#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::_geckoRole: \
return ariaRole;
switch (geckoRole) {
@@ -651,6 +649,12 @@ void Accessible::ApplyImplicitState(uint64_t& aState) const {
}
}
+bool Accessible::NameIsEmpty() const {
+ nsAutoString name;
+ Name(name);
+ return name.IsEmpty();
+}
+
////////////////////////////////////////////////////////////////////////////////
// KeyBinding class
diff --git a/accessible/basetypes/Accessible.h b/accessible/basetypes/Accessible.h
index 9b2e38e94d..77ac0bb4fc 100644
--- a/accessible/basetypes/Accessible.h
+++ b/accessible/basetypes/Accessible.h
@@ -253,6 +253,11 @@ class Accessible {
virtual ENameValueFlag Name(nsString& aName) const = 0;
/*
+ * Return true if the accessible name is empty.
+ */
+ bool NameIsEmpty() const;
+
+ /*
* Get the description of this accessible.
*/
virtual void Description(nsString& aDescription) const = 0;
diff --git a/accessible/generic/BaseAccessibles.cpp b/accessible/generic/BaseAccessibles.cpp
index 520f54e96b..eaaad02296 100644
--- a/accessible/generic/BaseAccessibles.cpp
+++ b/accessible/generic/BaseAccessibles.cpp
@@ -120,8 +120,7 @@ const LocalAccessible* LinkableAccessible::ActionWalk(bool* aIsLink,
}
KeyBinding LinkableAccessible::AccessKey() const {
- if (const LocalAccessible* actionAcc =
- const_cast<LinkableAccessible*>(this)->ActionWalk()) {
+ if (const LocalAccessible* actionAcc = ActionWalk()) {
return actionAcc->AccessKey();
}
diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp
index 28ed8bcbb4..0d84e8be9c 100644
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -67,6 +67,9 @@ static nsStaticAtom* const kRelationAttrs[] = {
static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
+static nsStaticAtom* const kSingleElementRelationIdlAttrs[] = {
+ nsGkAtoms::popovertarget};
+
////////////////////////////////////////////////////////////////////////////////
// Constructor/desctructor
@@ -383,25 +386,25 @@ void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc,
void DocAccessible::QueueCacheUpdateForDependentRelations(
LocalAccessible* aAcc) {
- if (!mIPCDoc || !aAcc || !aAcc->Elm() || !aAcc->IsInDocument() ||
- aAcc->IsDefunct()) {
+ if (!mIPCDoc || !aAcc || !aAcc->IsInDocument() || aAcc->IsDefunct()) {
return;
}
- nsAutoString ID;
- aAcc->DOMNodeID(ID);
- if (AttrRelProviders* list = GetRelProviders(aAcc->Elm(), ID)) {
- // We call this function when we've noticed an ID change, or when an acc
- // is getting bound to its document. We need to ensure any existing accs
- // that depend on this acc's ID have their rel cache entries updated.
- for (const auto& provider : *list) {
- LocalAccessible* relatedAcc = GetAccessible(provider->mContent);
- if (!relatedAcc || relatedAcc->IsDefunct() ||
- !relatedAcc->IsInDocument() ||
- mInsertedAccessibles.Contains(relatedAcc)) {
- continue;
- }
- QueueCacheUpdate(relatedAcc, CacheDomain::Relations);
+ dom::Element* el = aAcc->Elm();
+ if (!el) {
+ return;
+ }
+
+ // We call this function when we've noticed an ID change, or when an acc
+ // is getting bound to its document. We need to ensure any existing accs
+ // that depend on this acc's ID or Element have their relation cache entries
+ // updated.
+ RelatedAccIterator iter(this, el, nullptr);
+ while (LocalAccessible* relatedAcc = iter.Next()) {
+ if (relatedAcc->IsDefunct() || !relatedAcc->IsInDocument() ||
+ mInsertedAccessibles.Contains(relatedAcc)) {
+ continue;
}
+ QueueCacheUpdate(relatedAcc, CacheDomain::Relations);
}
}
@@ -507,6 +510,7 @@ void DocAccessible::Shutdown() {
}
mDependentIDsHashes.Clear();
+ mDependentElementsMap.Clear();
mNodeToAccessibleMap.Clear();
mAnchorJumpElm = nullptr;
@@ -729,9 +733,26 @@ std::pair<nsPoint, nsRect> DocAccessible::ComputeScrollData(
NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
+// When a reflected element IDL attribute changes, we might get the following
+// synchronous calls:
+// 1. AttributeWillChange for the element.
+// 2. AttributeWillChange for the content attribute.
+// 3. AttributeChanged for the content attribute.
+// 4. AttributeChanged for the element.
+// Since the content attribute value is "" for any element, we won't always get
+// 2 or 3. Even if we do, they might occur after the element has already
+// changed, which means we can't detect any relevant state changes there; e.g.
+// mPrevStateBits. Thus, we need 1 and 4, and we must ignore 2 and 3. To
+// facilitate this, sIsAttrElementChanging will be set to true for 2 and 3.
+static bool sIsAttrElementChanging = false;
+
void DocAccessible::AttributeWillChange(dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType) {
+ if (sIsAttrElementChanging) {
+ // See the comment above the definition of sIsAttrElementChanging.
+ return;
+ }
LocalAccessible* accessible = GetAccessible(aElement);
if (!accessible) {
if (aElement != mContent) return;
@@ -744,10 +765,11 @@ void DocAccessible::AttributeWillChange(dom::Element* aElement,
// elements.
if (aModType != dom::MutationEvent_Binding::ADDITION) {
RemoveDependentIDsFor(accessible, aAttribute);
+ RemoveDependentElementsFor(accessible, aAttribute);
}
if (aAttribute == nsGkAtoms::id) {
- if (accessible->IsActiveDescendant()) {
+ if (accessible->IsActiveDescendantId()) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(accessible, states::ACTIVE, false);
FireDelayedEvent(event);
@@ -778,6 +800,10 @@ void DocAccessible::AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue) {
+ if (sIsAttrElementChanging) {
+ // See the comment above the definition of sIsAttrElementChanging.
+ return;
+ }
NS_ASSERTION(!IsDefunct(),
"Attribute changed called on defunct document accessible!");
@@ -851,6 +877,7 @@ void DocAccessible::AttributeChanged(dom::Element* aElement,
if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
aModType == dom::MutationEvent_Binding::ADDITION) {
AddDependentIDsFor(accessible, aAttribute);
+ AddDependentElementsFor(accessible, aAttribute);
}
}
@@ -883,25 +910,23 @@ void DocAccessible::ARIAAttributeDefaultChanged(dom::Element* aElement,
void DocAccessible::ARIAActiveDescendantChanged(LocalAccessible* aAccessible) {
if (dom::Element* elm = aAccessible->Elm()) {
nsAutoString id;
- if (elm->GetAttr(nsGkAtoms::aria_activedescendant, id)) {
- dom::Element* activeDescendantElm = IDRefsIterator::GetElem(elm, id);
- if (activeDescendantElm) {
- LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm);
- if (activeDescendant) {
- RefPtr<AccEvent> event =
- new AccStateChangeEvent(activeDescendant, states::ACTIVE, true);
- FireDelayedEvent(event);
- if (aAccessible->IsActiveWidget()) {
- FocusMgr()->ActiveItemChanged(activeDescendant, false);
+ if (dom::Element* activeDescendantElm =
+ nsCoreUtils::GetAriaActiveDescendantElement(elm)) {
+ LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm);
+ if (activeDescendant) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(activeDescendant, states::ACTIVE, true);
+ FireDelayedEvent(event);
+ if (aAccessible->IsActiveWidget()) {
+ FocusMgr()->ActiveItemChanged(activeDescendant, false);
#ifdef A11Y_LOG
- if (logging::IsEnabled(logging::eFocus)) {
- logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
- activeDescendant);
- }
-#endif
+ if (logging::IsEnabled(logging::eFocus)) {
+ logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
+ activeDescendant);
}
- return;
+#endif
}
+ return;
}
}
@@ -991,6 +1016,11 @@ void DocAccessible::ElementStateChanged(dom::Document* aDocument,
new AccStateChangeEvent(accessible, states::DEFAULT);
FireDelayedEvent(event);
}
+
+ if (aStateMask.HasState(dom::ElementState::INDETERMINATE)) {
+ RefPtr<AccEvent> event = new AccStateChangeEvent(accessible, states::MIXED);
+ FireDelayedEvent(event);
+ }
}
void DocAccessible::CharacterDataWillChange(nsIContent* aContent,
@@ -1150,6 +1180,7 @@ void DocAccessible::BindToDocument(LocalAccessible* aAccessible,
if (aAccessible->HasOwnContent()) {
AddDependentIDsFor(aAccessible);
+ AddDependentElementsFor(aAccessible);
nsIContent* content = aAccessible->GetContent();
if (content->IsElement() &&
@@ -1769,6 +1800,61 @@ void DocAccessible::RemoveDependentIDsFor(LocalAccessible* aRelProvider,
}
}
+void DocAccessible::AddDependentElementsFor(LocalAccessible* aRelProvider,
+ nsAtom* aRelAttr) {
+ dom::Element* providerEl = aRelProvider->Elm();
+ if (!providerEl) {
+ return;
+ }
+ for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) {
+ if (aRelAttr && aRelAttr != attr) {
+ continue;
+ }
+ if (dom::Element* targetEl =
+ providerEl->GetExplicitlySetAttrElement(attr)) {
+ AttrRelProviders& providers =
+ mDependentElementsMap.LookupOrInsert(targetEl);
+ AttrRelProvider* provider = new AttrRelProvider(attr, providerEl);
+ providers.AppendElement(provider);
+ }
+ // If the relation attribute was given, we've already handled it. We don't
+ // have anything else to check.
+ if (aRelAttr) {
+ break;
+ }
+ }
+}
+
+void DocAccessible::RemoveDependentElementsFor(LocalAccessible* aRelProvider,
+ nsAtom* aRelAttr) {
+ dom::Element* providerEl = aRelProvider->Elm();
+ if (!providerEl) {
+ return;
+ }
+ for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) {
+ if (aRelAttr && aRelAttr != attr) {
+ continue;
+ }
+ if (dom::Element* targetEl =
+ providerEl->GetExplicitlySetAttrElement(attr)) {
+ if (auto providers = mDependentElementsMap.Lookup(targetEl)) {
+ providers.Data().RemoveElementsBy([attr,
+ providerEl](const auto& provider) {
+ return provider->mRelAttr == attr && provider->mContent == providerEl;
+ });
+ if (providers.Data().IsEmpty()) {
+ providers.Remove();
+ }
+ }
+ }
+ // If the relation attribute was given, we've already handled it. We don't
+ // have anything else to check.
+ if (aRelAttr) {
+ break;
+ }
+ }
+}
+
bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
nsAtom* aAttribute) {
if (aAttribute == nsGkAtoms::role) {
@@ -2003,7 +2089,7 @@ bool InsertIterator::Next() {
return false;
}
-void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible *aAcc) {
+void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible* aAcc) {
dom::Element* el = aAcc->Elm();
if (!el || !el->IsHTMLElement() || !el->HasAttr(nsGkAtoms::popover)) {
return; // Not a popover.
@@ -2620,6 +2706,7 @@ void DocAccessible::UncacheChildrenInSubtree(LocalAccessible* aRoot) {
MaybeFireEventsForChangedPopover(aRoot);
aRoot->mStateFlags |= eIsNotInDocument;
RemoveDependentIDsFor(aRoot);
+ RemoveDependentElementsFor(aRoot);
// The parent of the removed subtree is about to be cleared, so we must do
// this here rather than in LocalAccessible::UnbindFromParent because we need
@@ -2757,7 +2844,7 @@ void DocAccessible::DispatchScrollingEvent(nsINode* aTarget,
void DocAccessible::ARIAActiveDescendantIDMaybeMoved(
LocalAccessible* aAccessible) {
LocalAccessible* widget = nullptr;
- if (aAccessible->IsActiveDescendant(&widget) && widget) {
+ if (aAccessible->IsActiveDescendantId(&widget) && widget) {
// The active descendant might have just been inserted and may not be in the
// tree yet. Therefore, schedule this async to ensure the tree is up to
// date.
@@ -2845,3 +2932,22 @@ void DocAccessible::MaybeHandleChangeToHiddenNameOrDescription(
}
}
}
+
+void DocAccessible::AttrElementWillChange(dom::Element* aElement,
+ nsAtom* aAttr) {
+ MOZ_ASSERT(!sIsAttrElementChanging);
+ AttributeWillChange(aElement, kNameSpaceID_None, aAttr,
+ dom::MutationEvent_Binding::MODIFICATION);
+ // We might get notified about a related content attribute change. Ignore
+ // it.
+ sIsAttrElementChanging = true;
+}
+
+void DocAccessible::AttrElementChanged(dom::Element* aElement, nsAtom* aAttr) {
+ MOZ_ASSERT(sIsAttrElementChanging);
+ // The element has changed and the content attribute change notifications
+ // (if any) have been sent.
+ sIsAttrElementChanging = false;
+ AttributeChanged(aElement, kNameSpaceID_None, aAttr,
+ dom::MutationEvent_Binding::MODIFICATION, nullptr);
+}
diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h
index 52cbdd68cf..791d09661e 100644
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -121,7 +121,7 @@ class DocAccessible : public HyperTextAccessible,
void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain);
/**
- * Walks the mDependentIDsHashes list for the given accessible and
+ * Walks the dependent ids and elements maps for the given accessible and
* queues a CacheDomain::Relations cache update fore each related acc.
* We call this when we observe an ID mutation or when an acc is bound
* to its document.
@@ -410,6 +410,9 @@ class DocAccessible : public HyperTextAccessible,
return mMovedAccessibles.Contains(aAcc);
}
+ void AttrElementWillChange(dom::Element* aElement, nsAtom* aAttr);
+ void AttrElementChanged(dom::Element* aElement, nsAtom* aAttr);
+
protected:
virtual ~DocAccessible();
@@ -486,6 +489,35 @@ class DocAccessible : public HyperTextAccessible,
nsAtom* aRelAttr = nullptr);
/**
+ * Add dependent elements targeted by a relation attribute on an accessible
+ * element to the dependent elements cache. This is used for reflected IDL
+ * attributes which return DOM elements and reflect a content attribute, where
+ * the IDL attribute has been set to an element. For example, if the
+ * .popoverTargetElement IDL attribute is set to an element using JS, the
+ * target element will be added to the dependent elements cache. If the
+ * relation attribute is not specified, then all relation attributes are
+ * checked.
+ *
+ * @param aRelProvider [in] the accessible with the relation IDL attribute.
+ * @param aRelAttr [in, optional] the name of the reflected content attribute.
+ * For example, for the popoverTargetElement IDL attribute, this would be
+ * "popovertarget".
+ */
+ void AddDependentElementsFor(LocalAccessible* aRelProvider,
+ nsAtom* aRelAttr = nullptr);
+
+ /**
+ * Remove dependent elements targeted by a relation attribute on an accessible
+ * element from the dependent elements cache. If the relation attribute is
+ * not specified, then all relation attributes are checked.
+ *
+ * @param aRelProvider [in] the accessible with the relation IDL attribute.
+ * @param aRelAttr [in, optional] the name of the reflected content attribute.
+ */
+ void RemoveDependentElementsFor(LocalAccessible* aRelProvider,
+ nsAtom* aRelAttr = nullptr);
+
+ /**
* Update or recreate an accessible depending on a changed attribute.
*
* @param aElement [in] the element the attribute was changed on
@@ -727,12 +759,35 @@ class DocAccessible : public HyperTextAccessible,
void RemoveRelProvidersIfEmpty(dom::Element* aElement, const nsAString& aID);
/**
- * The cache of IDs pointed by relation attributes.
+ * A map used to look up the target node for an implicit reverse relation
+ * where the target of the explicit relation is specified as an id.
+ * For example:
+ * <div id="label">Name:</div><input aria-labelledby="label">
+ * The div should get a LABEL_FOR relation targeting the input. To facilitate
+ * that, mDependentIDsHashes maps from "label" to an AttrRelProvider
+ * specifying aria-labelledby and the input. Because ids are scoped to the
+ * nearest ancestor document or shadow root, mDependentIDsHashes maps from the
+ * DocumentOrShadowRoot first.
*/
nsClassHashtable<nsPtrHashKey<dom::DocumentOrShadowRoot>,
DependentIDsHashtable>
mDependentIDsHashes;
+ /**
+ * A map used to look up the target element for an implicit reverse relation
+ * where the target of the explicit relation is also specified as an element.
+ * This is similar to mDependentIDsHashes, except that this is used when a
+ * DOM property is used to set the relation target element directly, rather
+ * than using an id. For example:
+ * <button>More info</button><div popover>Some info</div>
+ * The button's .popoverTargetElement property is set to the div so that the
+ * button invokes the popover.
+ * To facilitate finding the invoker given the popover, mDependentElementsMap
+ * maps from the div to an AttrRelProvider specifying popovertarget and the
+ * button.
+ */
+ nsTHashMap<nsIContent*, AttrRelProviders> mDependentElementsMap;
+
friend class RelatedAccIterator;
/**
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
index f02f68faa6..a758164e2b 100644
--- a/accessible/generic/LocalAccessible.cpp
+++ b/accessible/generic/LocalAccessible.cpp
@@ -127,6 +127,7 @@ 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.
@@ -1840,10 +1841,40 @@ bool LocalAccessible::SetCurValue(double aValue) {
kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
}
+role LocalAccessible::FindNextValidARIARole(
+ std::initializer_list<nsStaticAtom*> aRolesToSkip) const {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && mContent && mContent->IsElement()) {
+ dom::Element* elem = mContent->AsElement();
+ if (!nsAccUtils::ARIAAttrValueIs(elem, nsGkAtoms::role,
+ roleMapEntry->roleAtom, eIgnoreCase)) {
+ // Get the next valid token that isn't in the list of roles to skip.
+ uint8_t roleMapIndex =
+ aria::GetFirstValidRoleMapIndexExcluding(elem, aRolesToSkip);
+ // If we don't find a valid token, fall back to the native role.
+ if (roleMapIndex == aria::NO_ROLE_MAP_ENTRY_INDEX ||
+ roleMapIndex == aria::LANDMARK_ROLE_MAP_ENTRY_INDEX) {
+ return NativeRole();
+ }
+ const nsRoleMapEntry* fallbackRoleMapEntry =
+ aria::GetRoleMapFromIndex(roleMapIndex);
+ if (!fallbackRoleMapEntry) {
+ return NativeRole();
+ }
+ // Return the next valid role, but validate that first, too.
+ return ARIATransformRole(fallbackRoleMapEntry->role);
+ }
+ }
+ // Fall back to the native role.
+ return NativeRole();
+}
+
role LocalAccessible::ARIATransformRole(role aRole) const {
// Beginning with ARIA 1.1, user agents are expected to use the native host
- // language role of the element when the region role is used without a name.
- // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region
+ // language role of the element when the form or region roles are used without
+ // a name. Says the spec, "the user agent MUST treat such elements as if no
+ // role had been provided."
+ // https://w3c.github.io/aria/#document-handling_author-errors_roles
//
// XXX: While the name computation algorithm can be non-trivial in the general
// case, it should not be especially bad here: If the author hasn't used the
@@ -1851,10 +1882,18 @@ role LocalAccessible::ARIATransformRole(role aRole) const {
// calculation rule excludes name from content. That said, this use case is
// another example of why we should consider caching the accessible name. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
- if (aRole == roles::REGION) {
- nsAutoString name;
- Name(name);
- return name.IsEmpty() ? NativeRole() : aRole;
+ if (aRole == roles::REGION || aRole == roles::FORM) {
+ if (NameIsEmpty()) {
+ // If we have a "form" or "region" role, but no accessible name, we need
+ // to search for the next valid role. First, we search through the role
+ // attribute value string - there might be a valid fallback there. Skip
+ // all "form" or "region" attributes; we know they're not valid since
+ // there's no accessible name. If we find a valid role that's not "form"
+ // or "region", fall back to it (but run it through ARIATransformRole
+ // first). Otherwise, fall back to the element's native role.
+ return FindNextValidARIARole({nsGkAtoms::region, nsGkAtoms::form});
+ }
+ return aRole;
}
// XXX: these unfortunate exceptions don't fit into the ARIA table. This is
@@ -2510,6 +2549,10 @@ void LocalAccessible::Shutdown() {
// LocalAccessible protected
void LocalAccessible::ARIAName(nsString& aName) const {
+ // 'slot' elements should ignore aria-label and aria-labelledby.
+ if (mContent->IsHTMLElement(nsGkAtoms::slot)) {
+ return;
+ }
// aria-labelledby now takes precedence over aria-label
nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
this, nsGkAtoms::aria_labelledby, aName);
@@ -3010,11 +3053,10 @@ LocalAccessible* LocalAccessible::CurrentItem() const {
// For activedescendant, the ARIA spec does not require that the user agent
// checks whether pointed node is actually a DOM descendant of the element
// with the aria-activedescendant attribute.
- nsAutoString id;
- if (HasOwnContent() && mContent->IsElement() &&
- mContent->AsElement()->GetAttr(nsGkAtoms::aria_activedescendant, id)) {
- dom::Element* activeDescendantElm = IDRefsIterator::GetElem(mContent, id);
- if (activeDescendantElm) {
+ if (HasOwnContent() && mContent->IsElement()) {
+ if (dom::Element* activeDescendantElm =
+ nsCoreUtils::GetAriaActiveDescendantElement(
+ mContent->AsElement())) {
if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
// Don't want a cyclical descendant relationship. That would be bad.
return nullptr;
@@ -3043,8 +3085,8 @@ LocalAccessible* LocalAccessible::ContainerWidget() const {
parent = parent->LocalParent()) {
nsIContent* parentContent = parent->GetContent();
if (parentContent && parentContent->IsElement() &&
- parentContent->AsElement()->HasAttr(
- nsGkAtoms::aria_activedescendant)) {
+ nsCoreUtils::GetAriaActiveDescendantElement(
+ parentContent->AsElement())) {
return parent;
}
@@ -3055,7 +3097,7 @@ LocalAccessible* LocalAccessible::ContainerWidget() const {
return nullptr;
}
-bool LocalAccessible::IsActiveDescendant(LocalAccessible** aWidget) const {
+bool LocalAccessible::IsActiveDescendantId(LocalAccessible** aWidget) const {
if (!HasOwnContent() || !mContent->HasID()) {
return false;
}
diff --git a/accessible/generic/LocalAccessible.h b/accessible/generic/LocalAccessible.h
index a3620f4cbd..51c4cc9424 100644
--- a/accessible/generic/LocalAccessible.h
+++ b/accessible/generic/LocalAccessible.h
@@ -587,7 +587,12 @@ class LocalAccessible : public nsISupports, public Accessible {
*/
virtual LocalAccessible* ContainerWidget() const;
- bool IsActiveDescendant(LocalAccessible** aWidget = nullptr) const;
+ /**
+ * Accessible's element ID is referenced as a aria-activedescendant in the
+ * document. This method is only used for ID changes and therefore does not
+ * need to work for direct element references via ariaActiveDescendantElement.
+ */
+ bool IsActiveDescendantId(LocalAccessible** aWidget = nullptr) const;
/**
* Return true if the accessible is defunct.
@@ -1010,6 +1015,17 @@ class LocalAccessible : public nsISupports, public Accessible {
*/
nsIFrame* FindNearestAccessibleAncestorFrame();
+ /*
+ * This function assumes that the current role is not valid. It searches for a
+ * fallback role in the role attribute string, and returns it. If there is no
+ * valid fallback role in the role attribute string, the function returns the
+ * native role. The aRolesToSkip parameter will cause the function to skip any
+ * roles found in the role attribute string when searching for the next valid
+ * role.
+ */
+ role FindNextValidARIARole(
+ std::initializer_list<nsStaticAtom*> aRolesToSkip) const;
+
LocalAccessible* GetPopoverTargetDetailsRelation() const;
};
diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build
index 06e229b999..6d34fe7d68 100644
--- a/accessible/generic/moz.build
+++ b/accessible/generic/moz.build
@@ -53,6 +53,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/html/HTMLElementAccessibles.cpp b/accessible/html/HTMLElementAccessibles.cpp
index e01de31ff2..135f997358 100644
--- a/accessible/html/HTMLElementAccessibles.cpp
+++ b/accessible/html/HTMLElementAccessibles.cpp
@@ -221,11 +221,38 @@ role HTMLHeaderOrFooterAccessible::NativeRole() const {
}
////////////////////////////////////////////////////////////////////////////////
+// HTMLAsideAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role HTMLAsideAccessible::NativeRole() const {
+ // Per the HTML-AAM spec, there are two cases for aside elements:
+ // 1. scoped to body or main elements -> 'complementary' role
+ // 2. scoped to sectioning content elements
+ // -> if the element has an accessible name, 'complementary' role
+ // -> otherwise, 'generic' role
+ // To implement this, walk ancestors until we find a sectioning content
+ // element, or a body/main element, then take actions based on the rules
+ // above.
+ nsIContent* parent = mContent->GetParent();
+ while (parent) {
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::aside,
+ nsGkAtoms::nav, nsGkAtoms::section)) {
+ return !NameIsEmpty() ? roles::LANDMARK : roles::SECTION;
+ }
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::main, nsGkAtoms::body)) {
+ return roles::LANDMARK;
+ }
+ parent = parent->GetParent();
+ }
+
+ // Fall back to landmark, though we always expect to find a body element.
+ return roles::LANDMARK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
// HTMLSectionAccessible
////////////////////////////////////////////////////////////////////////////////
role HTMLSectionAccessible::NativeRole() const {
- nsAutoString name;
- const_cast<HTMLSectionAccessible*>(this)->Name(name);
- return name.IsEmpty() ? roles::SECTION : roles::REGION;
+ return NameIsEmpty() ? roles::SECTION : roles::REGION;
}
diff --git a/accessible/html/HTMLElementAccessibles.h b/accessible/html/HTMLElementAccessibles.h
index 5a3ec6cef8..520a38342c 100644
--- a/accessible/html/HTMLElementAccessibles.h
+++ b/accessible/html/HTMLElementAccessibles.h
@@ -136,6 +136,23 @@ class HTMLHeaderOrFooterAccessible : public HyperTextAccessible {
};
/**
+ * Used for aside elements.
+ */
+class HTMLAsideAccessible : public HyperTextAccessible {
+ public:
+ HTMLAsideAccessible(nsIContent* aContent, DocAccessible* aDoc)
+ : HyperTextAccessible(aContent, aDoc) {}
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(HTMLAsideAccessible, HyperTextAccessible)
+
+ // LocalAccessible
+ virtual a11y::role NativeRole() const override;
+
+ protected:
+ virtual ~HTMLAsideAccessible() = default;
+};
+
+/**
* Used for HTML section element.
*/
class HTMLSectionAccessible : public HyperTextAccessible {
diff --git a/accessible/html/HTMLFormControlAccessible.cpp b/accessible/html/HTMLFormControlAccessible.cpp
index 95d6fed7b3..5974cc2f7c 100644
--- a/accessible/html/HTMLFormControlAccessible.cpp
+++ b/accessible/html/HTMLFormControlAccessible.cpp
@@ -35,9 +35,7 @@ using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
role HTMLFormAccessible::NativeRole() const {
- nsAutoString name;
- const_cast<HTMLFormAccessible*>(this)->Name(name);
- return name.IsEmpty() ? roles::FORM : roles::FORM_LANDMARK;
+ return NameIsEmpty() ? roles::FORM : roles::FORM_LANDMARK;
}
void HTMLFormAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
@@ -293,7 +291,7 @@ already_AddRefed<AccAttributes> HTMLTextFieldAccessible::NativeAttributes() {
nsString placeholderText;
if (mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholderText)) {
nsAutoString name;
- const_cast<HTMLTextFieldAccessible*>(this)->Name(name);
+ Name(name);
if (!name.Equals(placeholderText)) {
attributes->SetAttribute(nsGkAtoms::placeholder,
std::move(placeholderText));
diff --git a/accessible/html/HTMLLinkAccessible.cpp b/accessible/html/HTMLLinkAccessible.cpp
index b3549996fc..89c5d8d51d 100644
--- a/accessible/html/HTMLLinkAccessible.cpp
+++ b/accessible/html/HTMLLinkAccessible.cpp
@@ -112,6 +112,17 @@ void HTMLLinkAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
}
}
+ENameValueFlag HTMLLinkAccessible::NativeName(nsString& aName) const {
+ if (mContent->IsSVGElement()) {
+ mContent->AsElement()->GetAttr(kNameSpaceID_XLink, nsGkAtoms::title, aName);
+ if (!aName.IsEmpty()) {
+ return eNameOK;
+ }
+ }
+
+ return HyperTextAccessible::NativeName(aName);
+}
+
////////////////////////////////////////////////////////////////////////////////
// HyperLinkAccessible
diff --git a/accessible/html/HTMLLinkAccessible.h b/accessible/html/HTMLLinkAccessible.h
index de5f903a3d..888df2f06c 100644
--- a/accessible/html/HTMLLinkAccessible.h
+++ b/accessible/html/HTMLLinkAccessible.h
@@ -46,6 +46,8 @@ class HTMLLinkAccessible : public HyperTextAccessible {
const nsAttrValue* aOldValue,
uint64_t aOldState) override;
+ virtual ENameValueFlag NativeName(nsString& aName) const override;
+
enum { eAction_Jump = 0 };
};
diff --git a/accessible/html/moz.build b/accessible/html/moz.build
index 3a246373da..73b7737987 100644
--- a/accessible/html/moz.build
+++ b/accessible/html/moz.build
@@ -42,6 +42,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/interfaces/nsIAccessibleRole.idl b/accessible/interfaces/nsIAccessibleRole.idl
index e8024063c1..9c3376ab48 100644
--- a/accessible/interfaces/nsIAccessibleRole.idl
+++ b/accessible/interfaces/nsIAccessibleRole.idl
@@ -799,4 +799,11 @@ interface nsIAccessibleRole : nsISupports
* Represents a specific point in time.
*/
const unsigned long ROLE_TIME = 137;
+
+ /**
+ * Represents a composite widget containing a collection of one or more rows
+ * with one or more cells where some or all cells in the grid are focusable
+ * by using methods of two-dimensional navigation.
+ */
+ const unsigned long ROLE_GRID = 138;
};
diff --git a/accessible/ios/.clang-format b/accessible/ios/.clang-format
new file mode 100644
index 0000000000..269bce4d0f
--- /dev/null
+++ b/accessible/ios/.clang-format
@@ -0,0 +1,11 @@
+---
+# Objective C formatting rules.
+# Since this doesn't derive from the Cpp section, we need to redifine the root rules here.
+Language: ObjC
+BasedOnStyle: Google
+
+DerivePointerAlignment: false
+PointerAlignment: Left
+SortIncludes: false
+ColumnLimit: 80
+IndentPPDirectives: AfterHash
diff --git a/accessible/ios/AccessibleWrap.h b/accessible/ios/AccessibleWrap.h
new file mode 100644
index 0000000000..e5a55a4d2f
--- /dev/null
+++ b/accessible/ios/AccessibleWrap.h
@@ -0,0 +1,47 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_AccessibleWrap_h_
+#define mozilla_a11y_AccessibleWrap_h_
+
+#include <objc/objc.h>
+
+#include "nsCOMPtr.h"
+#include "LocalAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public LocalAccessible {
+ public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap() = default;
+
+ virtual void Shutdown() override;
+
+ /**
+ * Get the native Obj-C object (MUIAccessible).
+ */
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ protected:
+ id GetNativeObject();
+
+ private:
+ id mNativeObject;
+
+ bool mNativeInited;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/AccessibleWrap.mm b/accessible/ios/AccessibleWrap.mm
new file mode 100644
index 0000000000..576e854c60
--- /dev/null
+++ b/accessible/ios/AccessibleWrap.mm
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "AccessibleWrap.h"
+#include "LocalAccessible-inl.h"
+
+#import "MUIAccessible.h"
+#import "MUIRootAccessible.h"
+
+using namespace mozilla::a11y;
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : LocalAccessible(aContent, aDoc),
+ mNativeObject(nil),
+ mNativeInited(false) {}
+
+void AccessibleWrap::Shutdown() {
+ // this ensures we will not try to re-create the native object.
+ mNativeInited = true;
+
+ // we really intend to access the member directly.
+ if (mNativeObject) {
+ [mNativeObject expire];
+ [mNativeObject release];
+ mNativeObject = nil;
+ }
+
+ LocalAccessible::Shutdown();
+}
+
+id AccessibleWrap::GetNativeObject() {
+ if (!mNativeInited && !IsDefunct()) {
+ Class type = IsRoot() ? [MUIRootAccessible class] : [MUIAccessible class];
+ mNativeObject = [[type alloc] initWithAccessible:this];
+ }
+
+ mNativeInited = true;
+
+ return mNativeObject;
+}
+
+void AccessibleWrap::GetNativeInterface(void** aOutInterface) {
+ *aOutInterface = static_cast<void*>(GetNativeObject());
+}
diff --git a/accessible/ios/ApplicationAccessibleWrap.h b/accessible/ios/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..fd5ced8eb2
--- /dev/null
+++ b/accessible/ios/ApplicationAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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_ApplicationAccessibleWrap_h__
+#define mozilla_a11y_ApplicationAccessibleWrap_h__
+
+#include "ApplicationAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+using ApplicationAccessibleWrap = ApplicationAccessible;
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/DocAccessibleWrap.h b/accessible/ios/DocAccessibleWrap.h
new file mode 100644
index 0000000000..e14dfd4394
--- /dev/null
+++ b/accessible/ios/DocAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef DocAccessible DocAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/MUIAccessible.h b/accessible/ios/MUIAccessible.h
new file mode 100644
index 0000000000..725b06c345
--- /dev/null
+++ b/accessible/ios/MUIAccessible.h
@@ -0,0 +1,72 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 _MUIAccessible_H_
+#define _MUIAccessible_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIAccessibility.h>
+
+#include "AccessibleWrap.h"
+#include "RemoteAccessible.h"
+
+@class MUIAccessible;
+
+namespace mozilla {
+namespace a11y {
+
+inline MUIAccessible* _Nullable GetNativeFromGeckoAccessible(
+ mozilla::a11y::Accessible* _Nullable aAcc) {
+ if (!aAcc) {
+ return nil;
+ }
+ if (LocalAccessible* localAcc = aAcc->AsLocal()) {
+ MUIAccessible* native = nil;
+ localAcc->GetNativeInterface((void**)&native);
+ return native;
+ }
+
+ RemoteAccessible* remoteAcc = aAcc->AsRemote();
+ return reinterpret_cast<MUIAccessible*>(remoteAcc->GetWrapper());
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+@interface MUIAccessible : NSObject {
+ mozilla::a11y::Accessible* mGeckoAccessible;
+}
+
+// inits with the given accessible
+- (nonnull id)initWithAccessible:(nonnull mozilla::a11y::Accessible*)aAcc;
+
+// allows for gecko accessible access outside of the class
+- (mozilla::a11y::Accessible* _Nullable)geckoAccessible;
+
+- (void)expire;
+
+// override
+- (void)dealloc;
+
+// UIAccessibility
+- (BOOL)isAccessibilityElement;
+- (nullable NSString*)accessibilityLabel;
+- (nullable NSString*)accessibilityHint;
+- (CGRect)accessibilityFrame;
+- (nullable NSString*)accessibilityValue;
+- (uint64_t)accessibilityTraits;
+
+// UIAccessibilityContainer
+- (NSInteger)accessibilityElementCount;
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index;
+- (NSInteger)indexOfAccessibilityElement:(nonnull id)element;
+- (nullable NSArray*)accessibilityElements;
+- (UIAccessibilityContainerType)accessibilityContainerType;
+
+@end
+
+#endif
diff --git a/accessible/ios/MUIAccessible.mm b/accessible/ios/MUIAccessible.mm
new file mode 100644
index 0000000000..46c4712e2e
--- /dev/null
+++ b/accessible/ios/MUIAccessible.mm
@@ -0,0 +1,497 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MUIAccessible.h"
+
+#include "nsString.h"
+#include "RootAccessibleWrap.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+#ifdef A11Y_LOG
+# define DEBUG_HINTS
+#endif
+
+#ifdef DEBUG_HINTS
+static NSString* ToNSString(const nsACString& aCString) {
+ if (aCString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [[[NSString alloc] initWithBytes:aCString.BeginReading()
+ length:aCString.Length()
+ encoding:NSUTF8StringEncoding] autorelease];
+}
+#endif
+
+static NSString* ToNSString(const nsAString& aString) {
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
+ aString.BeginReading())
+ length:aString.Length()];
+}
+
+// These rules offer conditions for whether a gecko accessible
+// should be considered a UIKit accessibility element. Each role is mapped to a
+// rule.
+enum class IsAccessibilityElementRule {
+ // Always yes
+ Yes,
+ // Always no
+ No,
+ // If the accessible has no children. For example an empty header
+ // which is labeled.
+ IfChildless,
+ // If the accessible has no children and it is named and focusable.
+ IfChildlessWithNameAndFocusable,
+ // If this accessible isn't a child of an accessibility element. For example,
+ // a text leaf child of a button.
+ IfParentIsntElementWithName,
+ // If this accessible has multiple leafs that should functionally be
+ // united, for example a link with span elements.
+ IfBrokenUp,
+};
+
+class Trait {
+ public:
+ static const uint64_t None = 0;
+ static const uint64_t Button = ((uint64_t)0x1) << 0;
+ static const uint64_t Link = ((uint64_t)0x1) << 1;
+ static const uint64_t Image = ((uint64_t)0x1) << 2;
+ static const uint64_t Selected = ((uint64_t)0x1) << 3;
+ static const uint64_t PlaysSound = ((uint64_t)0x1) << 4;
+ static const uint64_t KeyboardKey = ((uint64_t)0x1) << 5;
+ static const uint64_t StaticText = ((uint64_t)0x1) << 6;
+ static const uint64_t SummaryElement = ((uint64_t)0x1) << 7;
+ static const uint64_t NotEnabled = ((uint64_t)0x1) << 8;
+ static const uint64_t UpdatesFrequently = ((uint64_t)0x1) << 9;
+ static const uint64_t SearchField = ((uint64_t)0x1) << 10;
+ static const uint64_t StartsMediaSession = ((uint64_t)0x1) << 11;
+ static const uint64_t Adjustable = ((uint64_t)0x1) << 12;
+ static const uint64_t AllowsDirectInteraction = ((uint64_t)0x1) << 13;
+ static const uint64_t CausesPageTurn = ((uint64_t)0x1) << 14;
+ static const uint64_t TabBar = ((uint64_t)0x1) << 15;
+ static const uint64_t Header = ((uint64_t)0x1) << 16;
+ static const uint64_t WebContent = ((uint64_t)0x1) << 17;
+ static const uint64_t TextEntry = ((uint64_t)0x1) << 18;
+ static const uint64_t PickerElement = ((uint64_t)0x1) << 19;
+ static const uint64_t RadioButton = ((uint64_t)0x1) << 20;
+ static const uint64_t IsEditing = ((uint64_t)0x1) << 21;
+ static const uint64_t LaunchIcon = ((uint64_t)0x1) << 22;
+ static const uint64_t StatusBarElement = ((uint64_t)0x1) << 23;
+ static const uint64_t SecureTextField = ((uint64_t)0x1) << 24;
+ static const uint64_t Inactive = ((uint64_t)0x1) << 25;
+ static const uint64_t Footer = ((uint64_t)0x1) << 26;
+ static const uint64_t BackButton = ((uint64_t)0x1) << 27;
+ static const uint64_t TabButton = ((uint64_t)0x1) << 28;
+ static const uint64_t AutoCorrectCandidate = ((uint64_t)0x1) << 29;
+ static const uint64_t DeleteKey = ((uint64_t)0x1) << 30;
+ static const uint64_t SelectionDismissesItem = ((uint64_t)0x1) << 31;
+ static const uint64_t Visited = ((uint64_t)0x1) << 32;
+ static const uint64_t Scrollable = ((uint64_t)0x1) << 33;
+ static const uint64_t Spacer = ((uint64_t)0x1) << 34;
+ static const uint64_t TableIndex = ((uint64_t)0x1) << 35;
+ static const uint64_t Map = ((uint64_t)0x1) << 36;
+ static const uint64_t TextOperationsAvailable = ((uint64_t)0x1) << 37;
+ static const uint64_t Draggable = ((uint64_t)0x1) << 38;
+ static const uint64_t GesturePracticeRegion = ((uint64_t)0x1) << 39;
+ static const uint64_t PopupButton = ((uint64_t)0x1) << 40;
+ static const uint64_t AllowsNativeSliding = ((uint64_t)0x1) << 41;
+ static const uint64_t MathEquation = ((uint64_t)0x1) << 42;
+ static const uint64_t ContainedByTable = ((uint64_t)0x1) << 43;
+ static const uint64_t ContainedByList = ((uint64_t)0x1) << 44;
+ static const uint64_t TouchContainer = ((uint64_t)0x1) << 45;
+ static const uint64_t SupportsZoom = ((uint64_t)0x1) << 46;
+ static const uint64_t TextArea = ((uint64_t)0x1) << 47;
+ static const uint64_t BookContent = ((uint64_t)0x1) << 48;
+ static const uint64_t ContainedByLandmark = ((uint64_t)0x1) << 49;
+ static const uint64_t FolderIcon = ((uint64_t)0x1) << 50;
+ static const uint64_t ReadOnly = ((uint64_t)0x1) << 51;
+ static const uint64_t MenuItem = ((uint64_t)0x1) << 52;
+ static const uint64_t Toggle = ((uint64_t)0x1) << 53;
+ static const uint64_t IgnoreItemChooser = ((uint64_t)0x1) << 54;
+ static const uint64_t SupportsTrackingDetail = ((uint64_t)0x1) << 55;
+ static const uint64_t Alert = ((uint64_t)0x1) << 56;
+ static const uint64_t ContainedByFieldset = ((uint64_t)0x1) << 57;
+ static const uint64_t AllowsLayoutChangeInStatusBar = ((uint64_t)0x1) << 58;
+};
+
+#pragma mark -
+
+@interface NSObject (AccessibilityPrivate)
+- (void)_accessibilityUnregister;
+@end
+
+@implementation MUIAccessible
+
+- (id)initWithAccessible:(Accessible*)aAcc {
+ MOZ_ASSERT(aAcc, "Cannot init MUIAccessible with null");
+ if ((self = [super init])) {
+ mGeckoAccessible = aAcc;
+ }
+
+ return self;
+}
+
+- (mozilla::a11y::Accessible*)geckoAccessible {
+ return mGeckoAccessible;
+}
+
+- (void)expire {
+ mGeckoAccessible = nullptr;
+ if ([self respondsToSelector:@selector(_accessibilityUnregister)]) {
+ [self _accessibilityUnregister];
+ }
+}
+
+- (void)dealloc {
+ [super dealloc];
+}
+
+static bool isAccessibilityElementInternal(Accessible* aAccessible) {
+ MOZ_ASSERT(aAccessible);
+ IsAccessibilityElementRule rule = IsAccessibilityElementRule::No;
+
+#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
+ case roles::_geckoRole: \
+ rule = iosIsElement; \
+ break;
+ switch (aAccessible->Role()) {
+#include "RoleMap.h"
+ }
+
+ switch (rule) {
+ case IsAccessibilityElementRule::Yes:
+ return true;
+ case IsAccessibilityElementRule::No:
+ return false;
+ case IsAccessibilityElementRule::IfChildless:
+ return aAccessible->ChildCount() == 0;
+ case IsAccessibilityElementRule::IfParentIsntElementWithName: {
+ nsAutoString name;
+ aAccessible->Name(name);
+ name.CompressWhitespace();
+ if (name.IsEmpty()) {
+ return false;
+ }
+
+ if (isAccessibilityElementInternal(aAccessible->Parent())) {
+ // This is a text leaf that needs to be pruned from a button or the
+ // likes. It should also be ignored in the event of its parent being a
+ // pruned link.
+ return false;
+ }
+
+ return true;
+ }
+ case IsAccessibilityElementRule::IfChildlessWithNameAndFocusable:
+ if (aAccessible->ChildCount() == 0 &&
+ (aAccessible->State() & states::FOCUSABLE)) {
+ nsAutoString name;
+ aAccessible->Name(name);
+ name.CompressWhitespace();
+ return !name.IsEmpty();
+ }
+ return false;
+ case IsAccessibilityElementRule::IfBrokenUp: {
+ uint32_t childCount = aAccessible->ChildCount();
+ if (childCount == 1) {
+ // If this is a single child container just use the text leaf and its
+ // traits will be inherited.
+ return false;
+ }
+
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = aAccessible->ChildAt(idx);
+ role accRole = child->Role();
+ if (accRole != roles::STATICTEXT && accRole != roles::TEXT_LEAF &&
+ accRole != roles::GRAPHIC) {
+ // If this container contains anything but text leafs and images
+ // ignore this accessible. Its descendants will inherit the
+ // container's traits.
+ return false;
+ }
+ }
+
+ return true;
+ }
+ default:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unhandled IsAccessibilityElementRule");
+
+ return false;
+}
+
+- (BOOL)isAccessibilityElement {
+ if (!mGeckoAccessible) {
+ return NO;
+ }
+
+ return isAccessibilityElementInternal(mGeckoAccessible) ? YES : NO;
+}
+
+- (NSString*)accessibilityLabel {
+ if (!mGeckoAccessible) {
+ return @"";
+ }
+
+ nsAutoString name;
+ mGeckoAccessible->Name(name);
+
+ return ToNSString(name);
+}
+
+- (NSString*)accessibilityHint {
+ if (!mGeckoAccessible) {
+ return @"";
+ }
+
+#ifdef DEBUG_HINTS
+ // Just put in a debug description as the label so we get a clue about which
+ // accessible ends up where.
+ nsAutoCString desc;
+ mGeckoAccessible->DebugDescription(desc);
+ return ToNSString(desc);
+#else
+ return @"";
+#endif
+}
+
+- (CGRect)accessibilityFrame {
+ RootAccessibleWrap* rootAcc = static_cast<RootAccessibleWrap*>(
+ mGeckoAccessible->IsLocal()
+ ? mGeckoAccessible->AsLocal()->RootAccessible()
+ : mGeckoAccessible->AsRemote()
+ ->OuterDocOfRemoteBrowser()
+ ->RootAccessible());
+
+ if (!rootAcc) {
+ return CGRectMake(0, 0, 0, 0);
+ }
+
+ LayoutDeviceIntRect rect = mGeckoAccessible->Bounds();
+ return rootAcc->DevPixelsRectToUIKit(rect);
+}
+
+- (NSString*)accessibilityValue {
+ if (!mGeckoAccessible) {
+ return nil;
+ }
+
+ uint64_t state = mGeckoAccessible->State();
+ if (state & states::LINKED) {
+ // Value returns the URL. We don't want to expose that as the value on iOS.
+ return nil;
+ }
+
+ if (state & states::CHECKABLE) {
+ if (state & states::CHECKED) {
+ return @"1";
+ }
+ if (state & states::MIXED) {
+ return @"2";
+ }
+ return @"0";
+ }
+
+ if (mGeckoAccessible->IsPassword()) {
+ // Accessible::Value returns an empty string. On iOS, we need to return the
+ // masked password so that AT knows how many characters are in the password.
+ Accessible* leaf = mGeckoAccessible->FirstChild();
+ if (!leaf) {
+ return nil;
+ }
+ nsAutoString masked;
+ leaf->AppendTextTo(masked);
+ return ToNSString(masked);
+ }
+
+ // If there is a heading ancestor, self has the header trait, so value should
+ // be the heading level.
+ for (Accessible* acc = mGeckoAccessible; acc; acc = acc->Parent()) {
+ if (acc->Role() == roles::HEADING) {
+ return [NSString stringWithFormat:@"%d", acc->GroupPosition().level];
+ }
+ }
+
+ nsAutoString value;
+ mGeckoAccessible->Value(value);
+ return ToNSString(value);
+}
+
+static uint64_t GetAccessibilityTraits(Accessible* aAccessible) {
+ uint64_t state = aAccessible->State();
+ uint64_t traits = Trait::WebContent;
+ switch (aAccessible->Role()) {
+ case roles::LINK:
+ traits |= Trait::Link;
+ break;
+ case roles::GRAPHIC:
+ traits |= Trait::Image;
+ break;
+ case roles::PAGETAB:
+ traits |= Trait::TabButton;
+ break;
+ case roles::PUSHBUTTON:
+ case roles::SUMMARY:
+ case roles::COMBOBOX:
+ case roles::BUTTONMENU:
+ case roles::TOGGLE_BUTTON:
+ case roles::CHECKBUTTON:
+ case roles::SWITCH:
+ traits |= Trait::Button;
+ break;
+ case roles::RADIOBUTTON:
+ traits |= Trait::RadioButton;
+ break;
+ case roles::HEADING:
+ traits |= Trait::Header;
+ break;
+ case roles::STATICTEXT:
+ case roles::TEXT_LEAF:
+ traits |= Trait::StaticText;
+ break;
+ case roles::SLIDER:
+ case roles::SPINBUTTON:
+ traits |= Trait::Adjustable;
+ break;
+ case roles::MENUITEM:
+ case roles::PARENT_MENUITEM:
+ case roles::CHECK_MENU_ITEM:
+ case roles::RADIO_MENU_ITEM:
+ traits |= Trait::MenuItem;
+ break;
+ case roles::PASSWORD_TEXT:
+ traits |= Trait::SecureTextField;
+ break;
+ default:
+ break;
+ }
+
+ if ((traits & Trait::Link) && (state & states::TRAVERSED)) {
+ traits |= Trait::Visited;
+ }
+
+ if ((traits & Trait::Button) && (state & states::HASPOPUP)) {
+ traits |= Trait::PopupButton;
+ }
+
+ if (state & states::SELECTED) {
+ traits |= Trait::Selected;
+ }
+
+ if (state & states::CHECKABLE) {
+ traits |= Trait::Toggle;
+ }
+
+ if (!(state & states::ENABLED)) {
+ traits |= Trait::NotEnabled;
+ }
+
+ if (state & states::EDITABLE) {
+ traits |= Trait::TextEntry;
+ if (state & states::FOCUSED) {
+ // XXX: Also add "has text cursor" trait
+ traits |= Trait::IsEditing | Trait::TextOperationsAvailable;
+ }
+
+ if (aAccessible->IsSearchbox()) {
+ traits |= Trait::SearchField;
+ }
+
+ if (state & states::MULTI_LINE) {
+ traits |= Trait::TextArea;
+ }
+ }
+
+ return traits;
+}
+
+- (uint64_t)accessibilityTraits {
+ if (!mGeckoAccessible) {
+ return Trait::None;
+ }
+
+ uint64_t traits = GetAccessibilityTraits(mGeckoAccessible);
+
+ for (Accessible* parent = mGeckoAccessible->Parent(); parent;
+ parent = parent->Parent()) {
+ traits |= GetAccessibilityTraits(parent);
+ }
+
+ return traits;
+}
+
+- (NSInteger)accessibilityElementCount {
+ return mGeckoAccessible ? mGeckoAccessible->ChildCount() : 0;
+}
+
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index {
+ if (!mGeckoAccessible) {
+ return nil;
+ }
+
+ Accessible* child = mGeckoAccessible->ChildAt(index);
+ return GetNativeFromGeckoAccessible(child);
+}
+
+- (NSInteger)indexOfAccessibilityElement:(id)element {
+ Accessible* acc = [(MUIAccessible*)element geckoAccessible];
+ if (!acc || mGeckoAccessible != acc->Parent()) {
+ return -1;
+ }
+
+ return acc->IndexInParent();
+}
+
+- (NSArray* _Nullable)accessibilityElements {
+ NSMutableArray* children = [[[NSMutableArray alloc] init] autorelease];
+ uint32_t childCount = mGeckoAccessible->ChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ if (MUIAccessible* child =
+ GetNativeFromGeckoAccessible(mGeckoAccessible->ChildAt(i))) {
+ [children addObject:child];
+ }
+ }
+
+ return children;
+}
+
+- (UIAccessibilityContainerType)accessibilityContainerType {
+ return UIAccessibilityContainerTypeNone;
+}
+
+- (NSRange)_accessibilitySelectedTextRange {
+ if (!mGeckoAccessible || !mGeckoAccessible->IsHyperText()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ // XXX This will only work in simple plain text boxes. It will break horribly
+ // if there are any embedded objects. Also, it only supports caret, not
+ // selection.
+ int32_t caret = mGeckoAccessible->AsHyperTextBase()->CaretOffset();
+ if (caret != -1) {
+ return NSMakeRange(caret, 0);
+ }
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)_accessibilitySetSelectedTextRange:(NSRange)range {
+ if (!mGeckoAccessible || !mGeckoAccessible->IsHyperText()) {
+ return;
+ }
+ // XXX This will only work in simple plain text boxes. It will break horribly
+ // if there are any embedded objects. Also, it only supports caret, not
+ // selection.
+ mGeckoAccessible->AsHyperTextBase()->SetCaretOffset(range.location);
+}
+
+@end
diff --git a/accessible/ios/MUIRootAccessible.h b/accessible/ios/MUIRootAccessible.h
new file mode 100644
index 0000000000..e3ce3b9c35
--- /dev/null
+++ b/accessible/ios/MUIRootAccessible.h
@@ -0,0 +1,29 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MUIAccessible.h"
+
+// our protocol that we implement (so uikit widgets can talk to us)
+#import "mozilla/a11y/MUIRootAccessibleProtocol.h"
+
+/*
+ The root accessible. It acts as a delegate to the UIKit child view.
+*/
+@interface MUIRootAccessible : MUIAccessible <MUIRootAccessibleProtocol> {
+ id<MUIRootAccessibleProtocol> mParallelView; // weak ref
+}
+
+// override
+- (id)initWithAccessible:(mozilla::a11y::Accessible*)aAcc;
+
+// override
+- (BOOL)hasRepresentedView;
+
+// override
+- (id)representedView;
+
+@end
diff --git a/accessible/ios/MUIRootAccessible.mm b/accessible/ios/MUIRootAccessible.mm
new file mode 100644
index 0000000000..35c87272e9
--- /dev/null
+++ b/accessible/ios/MUIRootAccessible.mm
@@ -0,0 +1,45 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "RootAccessibleWrap.h"
+
+#import "MUIRootAccessible.h"
+#import <UIKit/UIScreen.h>
+
+using namespace mozilla::a11y;
+
+static id<MUIRootAccessibleProtocol> getNativeViewFromRootAccessible(
+ LocalAccessible* aAccessible) {
+ RootAccessibleWrap* root =
+ static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
+ id<MUIRootAccessibleProtocol> nativeView = nil;
+ root->GetNativeWidget((void**)&nativeView);
+ return nativeView;
+}
+
+#pragma mark -
+
+@implementation MUIRootAccessible
+
+- (id)initWithAccessible:(mozilla::a11y::Accessible*)aAcc {
+ MOZ_ASSERT(!aAcc->IsRemote(), "MUIRootAccessible is never remote");
+
+ mParallelView = getNativeViewFromRootAccessible(aAcc->AsLocal());
+
+ return [super initWithAccessible:aAcc];
+}
+
+- (BOOL)hasRepresentedView {
+ return YES;
+}
+
+// this will return our parallel UIView.
+- (id)representedView {
+ return mParallelView;
+}
+
+@end
diff --git a/accessible/ios/MUIRootAccessibleProtocol.h b/accessible/ios/MUIRootAccessibleProtocol.h
new file mode 100644
index 0000000000..23451021ac
--- /dev/null
+++ b/accessible/ios/MUIRootAccessibleProtocol.h
@@ -0,0 +1,51 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIAccessibility.h>
+
+/* This protocol's primary use is so widget/cocoa can talk back to us
+ properly.
+
+ ChildView owns the topmost MUIRootAccessible, and needs to take care of
+ setting up that parent/child relationship.
+
+ This protocol is thus used to make sure it knows it's talking to us, and not
+ just some random |id|.
+*/
+
+@protocol MUIRootAccessibleProtocol <NSObject>
+
+- (BOOL)hasRepresentedView;
+
+- (nullable id)representedView;
+
+// UIAccessibility
+
+- (BOOL)isAccessibilityElement;
+
+- (nullable NSString*)accessibilityLabel;
+
+- (CGRect)accessibilityFrame;
+
+- (nullable NSString*)accessibilityValue;
+
+- (uint64_t)accessibilityTraits;
+
+// UIAccessibilityContainer
+
+- (NSInteger)accessibilityElementCount;
+
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index;
+
+- (NSInteger)indexOfAccessibilityElement:(nonnull id)element;
+
+- (nullable NSArray*)accessibilityElements;
+
+- (UIAccessibilityContainerType)accessibilityContainerType;
+
+@end
diff --git a/accessible/ios/Platform.mm b/accessible/ios/Platform.mm
new file mode 100644
index 0000000000..b4342ec555
--- /dev/null
+++ b/accessible/ios/Platform.mm
@@ -0,0 +1,57 @@
+/* -*- 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 "Platform.h"
+#include "RemoteAccessible.h"
+#include "DocAccessibleParent.h"
+
+#import "MUIAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+bool ShouldA11yBeEnabled() {
+ // XXX: Figure out proper a11y activation strategies in iOS.
+ return true;
+}
+
+void PlatformInit() {}
+
+void PlatformShutdown() {}
+
+void ProxyCreated(RemoteAccessible* aProxy) {
+ MUIAccessible* mozWrapper = [[MUIAccessible alloc] initWithAccessible:aProxy];
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
+}
+
+void ProxyDestroyed(RemoteAccessible* aProxy) {
+ MUIAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
+ [wrapper expire];
+ [wrapper release];
+ aProxy->SetWrapper(0);
+}
+
+void PlatformEvent(Accessible*, uint32_t) {}
+
+void PlatformStateChangeEvent(Accessible*, uint64_t, bool) {}
+
+void PlatformFocusEvent(Accessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect) {}
+
+void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset,
+ bool aIsSelectionCollapsed, int32_t aGranularity,
+ const LayoutDeviceIntRect& aCaretRect,
+ bool aFromUser) {}
+
+void PlatformTextChangeEvent(Accessible*, const nsAString&, int32_t, uint32_t,
+ bool, bool) {}
+
+void PlatformShowHideEvent(Accessible*, Accessible*, bool, bool) {}
+
+void PlatformSelectionEvent(Accessible*, Accessible*, uint32_t) {}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ios/RootAccessibleWrap.h b/accessible/ios/RootAccessibleWrap.h
new file mode 100644
index 0000000000..2353d5e791
--- /dev/null
+++ b/accessible/ios/RootAccessibleWrap.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+struct CGRect;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+/**
+ * iOS specific functionality for the node at a root of the accessibility
+ * tree: see the RootAccessible superclass for further details.
+ */
+class RootAccessibleWrap : public RootAccessible {
+ public:
+ RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+ virtual ~RootAccessibleWrap() = default;
+
+ // Lets our native accessible get in touch with the
+ // native cocoa view that is our accessible parent.
+ void GetNativeWidget(void** aOutView);
+
+ CGRect DevPixelsRectToUIKit(const LayoutDeviceIntRect& aRect);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/RootAccessibleWrap.mm b/accessible/ios/RootAccessibleWrap.mm
new file mode 100644
index 0000000000..1d2a404161
--- /dev/null
+++ b/accessible/ios/RootAccessibleWrap.mm
@@ -0,0 +1,50 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "RootAccessibleWrap.h"
+
+#include "MUIRootAccessible.h"
+
+#include "gfxPlatform.h"
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIWidget.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : RootAccessible(aDocument, aPresShell) {}
+
+void RootAccessibleWrap::GetNativeWidget(void** aOutView) {
+ nsIFrame* frame = GetFrame();
+ if (frame) {
+ nsView* view = frame->GetView();
+ if (view) {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ *aOutView = (void**)widget->GetNativeData(NS_NATIVE_WIDGET);
+ MOZ_DIAGNOSTIC_ASSERT(*aOutView, "Couldn't get the native UIView!");
+ }
+ }
+ }
+}
+
+CGRect RootAccessibleWrap::DevPixelsRectToUIKit(
+ const LayoutDeviceIntRect& aRect) {
+ UIView* nativeWidget = nil;
+ GetNativeWidget((void**)&nativeWidget);
+ CGRect rootFrame = [nativeWidget accessibilityFrame];
+ CGFloat scale = [nativeWidget contentScaleFactor];
+ return CGRectMake(((CGFloat)aRect.x / scale) + rootFrame.origin.x,
+ ((CGFloat)aRect.y / scale) + rootFrame.origin.y,
+ (CGFloat)aRect.width / scale,
+ (CGFloat)aRect.height / scale);
+}
diff --git a/accessible/ios/moz.build b/accessible/ios/moz.build
new file mode 100644
index 0000000000..fd94c6cc97
--- /dev/null
+++ b/accessible/ios/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.a11y += [
+ "AccessibleWrap.h",
+ "MUIRootAccessibleProtocol.h",
+]
+
+SOURCES += [
+ "AccessibleWrap.mm",
+ "MUIAccessible.mm",
+ "MUIRootAccessible.mm",
+ "Platform.mm",
+ "RootAccessibleWrap.mm",
+]
+
+LOCAL_INCLUDES += [
+ "/accessible/base",
+ "/accessible/generic",
+ "/accessible/html",
+ "/accessible/ipc",
+ "/accessible/xul",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build
index b8ff3b9c4f..462018fb93 100644
--- a/accessible/ipc/moz.build
+++ b/accessible/ipc/moz.build
@@ -18,6 +18,10 @@ else:
LOCAL_INCLUDES += [
"/accessible/mac",
]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/mac/GeckoTextMarker.mm b/accessible/mac/GeckoTextMarker.mm
index ba3a6e6231..bb787edd28 100644
--- a/accessible/mac/GeckoTextMarker.mm
+++ b/accessible/mac/GeckoTextMarker.mm
@@ -88,6 +88,13 @@ GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
// Iterate through all segments until we exhausted the index sum
// so we can find the segment the index lives in.
for (TextLeafRange segment : range) {
+ if (segment.Start().mAcc->IsMenuPopup() &&
+ (segment.Start().mAcc->State() & states::COLLAPSED)) {
+ // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
+ // them.
+ continue;
+ }
+
if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
// XXX: MacOS expects bullets to be in the range's text, but not in
// the calculated length!
@@ -392,6 +399,12 @@ NSString* GeckoTextMarkerRange::Text() const {
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
+ if (start.mAcc->IsMenuPopup() &&
+ (start.mAcc->State() & states::COLLAPSED)) {
+ // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
+ // them.
+ continue;
+ }
if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
continue;
}
@@ -441,6 +454,12 @@ NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
for (TextLeafRange segment : range) {
TextLeafPoint start = segment.Start();
TextLeafPoint attributesNext;
+ if (start.mAcc->IsMenuPopup() &&
+ (start.mAcc->State() & states::COLLAPSED)) {
+ // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
+ // them.
+ continue;
+ }
do {
if (start.mAcc->IsText()) {
attributesNext = start.FindTextAttrsStart(eDirNext, false);
diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm
index eb507adefb..9f9738ab36 100644
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -256,7 +256,7 @@ void PlatformRoleChangedEvent(Accessible* aTarget, const a11y::role& aRole,
mozilla::Telemetry::ScalarSet(
mozilla::Telemetry::ScalarID::A11Y_INSTANTIATORS, client);
#endif // defined(MOZ_TELEMETRY_REPORTING)
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::AccessibilityClient,
NS_ConvertUTF16toUTF8(client));
}
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm
index 2d8e140343..aa98496511 100644
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -295,7 +295,7 @@ using namespace mozilla::a11y;
- (NSString*)moxRole {
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
return macRole;
@@ -366,7 +366,7 @@ using namespace mozilla::a11y;
}
#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::geckoRole: \
if (![macSubrole isEqualToString:NSAccessibilityUnknownSubrole]) { \
return macSubrole; \
diff --git a/accessible/moz.build b/accessible/moz.build
index ed05b5b50e..000230672a 100644
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -14,6 +14,8 @@ elif toolkit == "cocoa":
DIRS += ["mac"]
elif toolkit == "android":
DIRS += ["android"]
+elif toolkit == "uikit":
+ DIRS += ["ios"]
else:
DIRS += ["other"]
diff --git a/accessible/tests/browser/atk/browser_role.js b/accessible/tests/browser/atk/browser_role.js
index 7b870b3337..47cc5ef28f 100644
--- a/accessible/tests/browser/atk/browser_role.js
+++ b/accessible/tests/browser/atk/browser_role.js
@@ -11,7 +11,7 @@ addAccessibleTask(
`
<p id="p">p</p>
`,
- async function (browser, docAcc) {
+ async function () {
let role = await runPython(`
global doc
doc = getDoc()
diff --git a/accessible/tests/browser/atk/browser_table.js b/accessible/tests/browser/atk/browser_table.js
index 98b3270465..55452709e8 100644
--- a/accessible/tests/browser/atk/browser_table.js
+++ b/accessible/tests/browser/atk/browser_table.js
@@ -20,7 +20,7 @@ addAccessibleTask(
</tr>
</table>
`,
- async function (browser, docAcc) {
+ async function () {
let result = await runPython(`
global doc
doc = getDoc()
diff --git a/accessible/tests/browser/bounds/browser_accessible_moved.js b/accessible/tests/browser/bounds/browser_accessible_moved.js
index 307c680000..a62f7ad8d0 100644
--- a/accessible/tests/browser/bounds/browser_accessible_moved.js
+++ b/accessible/tests/browser/bounds/browser_accessible_moved.js
@@ -12,10 +12,10 @@ function assertBoundsNonZero(acc) {
let width = {};
let height = {};
acc.getBounds(x, y, width, height);
- ok(x.value > 0, "x is non-0");
- ok(y.value > 0, "y is non-0");
- ok(width.value > 0, "width is non-0");
- ok(height.value > 0, "height is non-0");
+ Assert.greater(x.value, 0, "x is non-0");
+ Assert.greater(y.value, 0, "y is non-0");
+ Assert.greater(width.value, 0, "width is non-0");
+ Assert.greater(height.value, 0, "height is non-0");
}
/**
diff --git a/accessible/tests/browser/bounds/browser_caret_rect.js b/accessible/tests/browser/bounds/browser_caret_rect.js
index ac0ee3aa50..9f5cac33a5 100644
--- a/accessible/tests/browser/bounds/browser_caret_rect.js
+++ b/accessible/tests/browser/bounds/browser_caret_rect.js
@@ -46,7 +46,7 @@ async function testCaretRect(browser, docAcc, id, offset) {
);
const [caretX, caretY, caretW, caretH] = await getCaretRect(browser, id);
if (atEnd) {
- ok(caretX > charX.value, "Caret x after last character x");
+ Assert.greater(caretX, charX.value, "Caret x after last character x");
} else {
is(caretX, charX.value, "Caret x same as character x");
}
diff --git a/accessible/tests/browser/bounds/browser_test_display_contents.js b/accessible/tests/browser/bounds/browser_test_display_contents.js
index db1bfce178..4111ac5a81 100644
--- a/accessible/tests/browser/bounds/browser_test_display_contents.js
+++ b/accessible/tests/browser/bounds/browser_test_display_contents.js
@@ -16,7 +16,11 @@ async function testContentBounds(browser, acc) {
is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
is(width, expectedWidth, "Wrong width of " + prettyAccName);
- ok(height >= expectedHeight, "Wrong height of " + prettyAccName);
+ Assert.greaterOrEqual(
+ height,
+ expectedHeight,
+ "Wrong height of " + prettyAccName
+ );
}
async function runTests(browser, accDoc) {
diff --git a/accessible/tests/browser/bounds/browser_test_iframe_transform.js b/accessible/tests/browser/bounds/browser_test_iframe_transform.js
index a44ab75faf..015d466da9 100644
--- a/accessible/tests/browser/bounds/browser_test_iframe_transform.js
+++ b/accessible/tests/browser/bounds/browser_test_iframe_transform.js
@@ -68,7 +68,7 @@ function testBoundsWithOffset(browser, iframeDocAcc, id, domElmBounds, offset) {
addAccessibleTask(
`<div id='${ELEM_ID}'>hello world</div>`,
- async function (browser, iframeDocAcc, contentDocAcc) {
+ async function (browser, iframeDocAcc) {
ok(iframeDocAcc, "IFRAME document accessible is present");
await testBoundsWithContent(iframeDocAcc, ELEM_ID, browser);
@@ -143,7 +143,7 @@ addAccessibleTask(
*/
addAccessibleTask(
`<div id="div" style="width: 30px; height: 30px"></div>`,
- async function (browser, accDoc, foo) {
+ async function (browser, accDoc) {
const docWidth = () => {
let width = {};
accDoc.getBounds({}, {}, width, {});
diff --git a/accessible/tests/browser/bounds/browser_test_simple_transform.js b/accessible/tests/browser/bounds/browser_test_simple_transform.js
index 7197968b40..b5cb983e72 100644
--- a/accessible/tests/browser/bounds/browser_test_simple_transform.js
+++ b/accessible/tests/browser/bounds/browser_test_simple_transform.js
@@ -9,7 +9,7 @@ loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
// test basic translation
addAccessibleTask(
`<p id="translate">hello world</p>`,
- async function (browser, iframeDocAcc, contentDocAcc) {
+ async function (browser, iframeDocAcc) {
ok(iframeDocAcc, "IFRAME document accessible is present");
await testBoundsWithContent(iframeDocAcc, "translate", browser);
@@ -42,7 +42,7 @@ addAccessibleTask(
// test basic rotation
addAccessibleTask(
`<p id="rotate">hello world</p>`,
- async function (browser, iframeDocAcc, contentDocAcc) {
+ async function (browser, iframeDocAcc) {
ok(iframeDocAcc, "IFRAME document accessible is present");
await testBoundsWithContent(iframeDocAcc, "rotate", browser);
@@ -60,7 +60,7 @@ addAccessibleTask(
// test basic scale
addAccessibleTask(
`<p id="scale">hello world</p>`,
- async function (browser, iframeDocAcc, contentDocAcc) {
+ async function (browser, iframeDocAcc) {
ok(iframeDocAcc, "IFRAME document accessible is present");
await testBoundsWithContent(iframeDocAcc, "scale", browser);
diff --git a/accessible/tests/browser/bounds/browser_test_zoom.js b/accessible/tests/browser/bounds/browser_test_zoom.js
index ac84e485a4..1af84d61d8 100644
--- a/accessible/tests/browser/bounds/browser_test_zoom.js
+++ b/accessible/tests/browser/bounds/browser_test_zoom.js
@@ -16,7 +16,11 @@ async function testContentBounds(browser, acc) {
is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
is(width, expectedWidth, "Wrong width of " + prettyAccName);
- ok(height >= expectedHeight, "Wrong height of " + prettyAccName);
+ Assert.greaterOrEqual(
+ height,
+ expectedHeight,
+ "Wrong height of " + prettyAccName
+ );
}
async function runTests(browser, accDoc) {
diff --git a/accessible/tests/browser/bounds/browser_zero_area.js b/accessible/tests/browser/bounds/browser_zero_area.js
index c0f9db2673..80954fbd1a 100644
--- a/accessible/tests/browser/bounds/browser_zero_area.js
+++ b/accessible/tests/browser/bounds/browser_zero_area.js
@@ -67,10 +67,10 @@ addAccessibleTask(
const radio = findAccessibleChildByID(accDoc, "radio");
const contentDPR = await getContentDPR(browser);
const [x, y, width, height] = getBounds(radio, contentDPR);
- ok(x < 0, "X coordinate should be negative");
- ok(y > 0, "Y coordinate should be positive");
- ok(width > 0, "Width should be positive");
- ok(height > 0, "Height should be positive");
+ Assert.less(x, 0, "X coordinate should be negative");
+ Assert.greater(y, 0, "Y coordinate should be positive");
+ Assert.greater(width, 0, "Width should be positive");
+ Assert.greater(height, 0, "Height should be positive");
// Note: the exact values of x, y, width, and height
// are inconsistent with the DOM element values of those
// fields, so we don't check our bounds against them with
diff --git a/accessible/tests/browser/browser_shutdown_acc_reference.js b/accessible/tests/browser/browser_shutdown_acc_reference.js
index 1768095f94..975eab55a0 100644
--- a/accessible/tests/browser/browser_shutdown_acc_reference.js
+++ b/accessible/tests/browser/browser_shutdown_acc_reference.js
@@ -36,7 +36,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_doc_acc_reference.js b/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
index 8f7bf6d423..7d999d7d13 100644
--- a/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
+++ b/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
@@ -28,7 +28,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js b/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
index 273fc7175d..1200e02116 100644
--- a/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
@@ -39,7 +39,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js b/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
index af21b3dc4c..346aa9b6c4 100644
--- a/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
@@ -39,7 +39,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
index e4091c5216..84429cf077 100644
--- a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
@@ -30,7 +30,7 @@ add_task(async function () {
<body id="body"><div id="div"></div></body>
</html>`,
},
- async function (browser) {
+ async function () {
let docLoadedEvent = await docLoaded;
let docAcc = docLoadedEvent.accessibleDocument;
ok(docAcc, "Accessible document proxy is created");
@@ -46,7 +46,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
index f6eca362b0..b7356f3e6f 100644
--- a/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
@@ -30,7 +30,7 @@ add_task(async function () {
<body id="body"><div id="div"></div></body>
</html>`,
},
- async function (browser) {
+ async function () {
let docLoadedEvent = await docLoaded;
let docAcc = docLoadedEvent.accessibleDocument;
ok(docAcc, "Accessible document proxy is created");
@@ -46,7 +46,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_multi_reference.js b/accessible/tests/browser/browser_shutdown_multi_reference.js
index a92f6faf61..49ee822afa 100644
--- a/accessible/tests/browser/browser_shutdown_multi_reference.js
+++ b/accessible/tests/browser/browser_shutdown_multi_reference.js
@@ -31,7 +31,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_parent_own_reference.js b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
index 472e977626..ffbe33f137 100644
--- a/accessible/tests/browser/browser_shutdown_parent_own_reference.js
+++ b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
@@ -61,7 +61,7 @@ add_task(async function () {
shutdownAccService(browser);
await contentA11yShutdownObserver;
const contentA11yShutdown = new Promise((resolve, reject) =>
- contentA11yShutdownPromise.then(flag =>
+ contentA11yShutdownPromise.then(() =>
contentCanShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js b/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
index 7144cff019..47fa47da53 100644
--- a/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
+++ b/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
@@ -41,7 +41,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js b/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
index 6d4ad71f1e..72a424b21f 100644
--- a/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
+++ b/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
@@ -30,7 +30,7 @@ add_task(async function () {
<body id="body"></body>
</html>`,
},
- async function (browser) {
+ async function () {
let docLoadedEvent = await docLoaded;
let docAcc = docLoadedEvent.accessibleDocument;
ok(docAcc, "Accessible document proxy is created");
@@ -43,7 +43,7 @@ add_task(async function () {
const [a11yShutdownObserver, a11yShutdownPromise] = shutdownAccService();
await a11yShutdownObserver;
const a11yShutdown = new Promise((resolve, reject) =>
- a11yShutdownPromise.then(flag =>
+ a11yShutdownPromise.then(() =>
canShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/browser_shutdown_remote_own_reference.js b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
index a30d191b53..83c68689b7 100644
--- a/accessible/tests/browser/browser_shutdown_remote_own_reference.js
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -108,7 +108,7 @@ add_task(async function () {
const [contentA11yShutdownObserver, contentA11yShutdownPromise] =
shutdownAccService(browser);
const contentA11yShutdown = new Promise((resolve, reject) =>
- contentA11yShutdownPromise.then(flag =>
+ contentA11yShutdownPromise.then(() =>
contentCanShutdown
? resolve()
: reject("Accessible service was shut down incorrectly")
diff --git a/accessible/tests/browser/e10s/browser.toml b/accessible/tests/browser/e10s/browser.toml
index dfac6b5219..914f839993 100644
--- a/accessible/tests/browser/e10s/browser.toml
+++ b/accessible/tests/browser/e10s/browser.toml
@@ -18,9 +18,12 @@ support-files = [
]
prefs = [
"javascript.options.asyncstack_capture_debuggee_only=false",
- "dom.element.popover.enabled=true"
+ "dom.element.popover.enabled=true",
+ "accessibility.ARIAElementReflection.enabled=true"
]
+["browser_aria_activedescendant.js"]
+
# Caching tests
["browser_caching_actions.js"]
diff --git a/accessible/tests/browser/e10s/browser_aria_activedescendant.js b/accessible/tests/browser/e10s/browser_aria_activedescendant.js
new file mode 100644
index 0000000000..f58c5aab39
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_aria_activedescendant.js
@@ -0,0 +1,485 @@
+/* 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";
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+async function synthFocus(browser, container, item) {
+ let focusPromise = waitForEvent(EVENT_FOCUS, item);
+ await invokeContentTask(browser, [container], _container => {
+ let elm = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_container);
+ elm.focus();
+ });
+ await focusPromise;
+}
+
+async function changeARIAActiveDescendant(
+ browser,
+ container,
+ itemId,
+ prevItemId,
+ elementReflection
+) {
+ let expectedEvents = [[EVENT_FOCUS, itemId]];
+
+ if (prevItemId) {
+ info("A state change of the previous item precedes the new one.");
+ expectedEvents.push(
+ stateChangeEventArgs(prevItemId, EXT_STATE_ACTIVE, false, true)
+ );
+ }
+
+ expectedEvents.push(
+ stateChangeEventArgs(itemId, EXT_STATE_ACTIVE, true, true)
+ );
+
+ let expectedPromise = waitForEvents(expectedEvents);
+ await invokeContentTask(
+ browser,
+ [container, itemId, elementReflection],
+ (_container, _itemId, _elementReflection) => {
+ let getElm = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document);
+ let elm = getElm(_container);
+ if (_elementReflection) {
+ elm.ariaActiveDescendantElement = getElm(_itemId);
+ } else {
+ elm.setAttribute("aria-activedescendant", _itemId);
+ }
+ }
+ );
+
+ await expectedPromise;
+}
+
+async function clearARIAActiveDescendant(
+ browser,
+ container,
+ prevItemId,
+ defaultId,
+ elementReflection
+) {
+ let expectedEvents = [[EVENT_FOCUS, defaultId || container]];
+ if (prevItemId) {
+ expectedEvents.push(
+ stateChangeEventArgs(prevItemId, EXT_STATE_ACTIVE, false, true)
+ );
+ }
+
+ if (defaultId) {
+ expectedEvents.push(
+ stateChangeEventArgs(defaultId, EXT_STATE_ACTIVE, true, true)
+ );
+ }
+
+ let expectedPromise = waitForEvents(expectedEvents);
+ await invokeContentTask(
+ browser,
+ [container, elementReflection],
+ (_container, _elementReflection) => {
+ let elm = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_container);
+ if (_elementReflection) {
+ elm.ariaActiveDescendantElement = null;
+ } else {
+ elm.removeAttribute("aria-activedescendant");
+ }
+ }
+ );
+
+ await expectedPromise;
+}
+
+async function insertItemNFocus(
+ browser,
+ container,
+ newItemID,
+ prevItemId,
+ elementReflection
+) {
+ let expectedEvents = [
+ [EVENT_SHOW, newItemID],
+ [EVENT_FOCUS, newItemID],
+ ];
+
+ if (prevItemId) {
+ info("A state change of the previous item precedes the new one.");
+ expectedEvents.push(
+ stateChangeEventArgs(prevItemId, EXT_STATE_ACTIVE, false, true)
+ );
+ }
+
+ expectedEvents.push(
+ stateChangeEventArgs(newItemID, EXT_STATE_ACTIVE, true, true)
+ );
+
+ let expectedPromise = waitForEvents(expectedEvents);
+
+ await invokeContentTask(
+ browser,
+ [container, newItemID, elementReflection],
+ (_container, _newItemID, _elementReflection) => {
+ let elm = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_container);
+ let itemElm = content.document.createElement("div");
+ itemElm.setAttribute("id", _newItemID);
+ itemElm.setAttribute("role", "listitem");
+ itemElm.textContent = _newItemID;
+ elm.appendChild(itemElm);
+ if (_elementReflection) {
+ elm.ariaActiveDescendantElement = itemElm;
+ } else {
+ elm.setAttribute("aria-activedescendant", _newItemID);
+ }
+ }
+ );
+
+ await expectedPromise;
+}
+
+async function moveARIAActiveDescendantID(browser, fromID, toID) {
+ let expectedEvents = [
+ [EVENT_FOCUS, toID],
+ stateChangeEventArgs(toID, EXT_STATE_ACTIVE, true, true),
+ ];
+
+ let expectedPromise = waitForEvents(expectedEvents);
+ await invokeContentTask(browser, [fromID, toID], (_fromID, _toID) => {
+ let orig = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_toID);
+ if (orig) {
+ orig.id = "";
+ }
+ (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_fromID).id = _toID;
+ });
+ await expectedPromise;
+}
+
+async function changeARIAActiveDescendantInvalid(
+ browser,
+ container,
+ invalidID = "invalid",
+ prevItemId = null
+) {
+ let expectedEvents = [[EVENT_FOCUS, container]];
+ if (prevItemId) {
+ expectedEvents.push(
+ stateChangeEventArgs(prevItemId, EXT_STATE_ACTIVE, false, true)
+ );
+ }
+
+ let expectedPromise = waitForEvents(expectedEvents);
+ await invokeContentTask(
+ browser,
+ [container, invalidID],
+ (_container, _invalidID) => {
+ let elm = (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)(_container);
+ elm.setAttribute("aria-activedescendant", _invalidID);
+ }
+ );
+
+ await expectedPromise;
+}
+
+const LISTBOX_MARKUP = `
+<div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1"
+aria-owns="item3">
+<div role="listitem" id="item1">item1</div>
+<div role="listitem" id="item2">item2</div>
+<div role="listitem" id="roaming" data-id="roaming">roaming</div>
+<div role="listitem" id="roaming2" data-id="roaming2">roaming2</div>
+</div>
+<div role="listitem" id="item3">item3</div>
+<div role="combobox" id="combobox">
+<input id="combobox_entry">
+<ul>
+ <li role="option" id="combobox_option1">option1</li>
+ <li role="option" id="combobox_option2">option2</li>
+</ul>
+</div>`;
+
+async function basicListboxTest(browser, elementReflection) {
+ await synthFocus(browser, "listbox", "item1");
+ await changeARIAActiveDescendant(
+ browser,
+ "listbox",
+ "item2",
+ "item1",
+ elementReflection
+ );
+ await changeARIAActiveDescendant(
+ browser,
+ "listbox",
+ "item3",
+ "item2",
+ elementReflection
+ );
+
+ info("Focus out of listbox");
+ await synthFocus(browser, "combobox_entry", "combobox_entry");
+ await changeARIAActiveDescendant(
+ browser,
+ "combobox",
+ "combobox_option2",
+ null,
+ elementReflection
+ );
+ await changeARIAActiveDescendant(
+ browser,
+ "combobox",
+ "combobox_option1",
+ null,
+ elementReflection
+ );
+
+ info("Focus back in listbox");
+ await synthFocus(browser, "listbox", "item3");
+ await insertItemNFocus(
+ browser,
+ "listbox",
+ "item4",
+ "item3",
+ elementReflection
+ );
+
+ await clearARIAActiveDescendant(
+ browser,
+ "listbox",
+ "item4",
+ null,
+ elementReflection
+ );
+ await changeARIAActiveDescendant(
+ browser,
+ "listbox",
+ "item1",
+ null,
+ elementReflection
+ );
+}
+
+addAccessibleTask(
+ LISTBOX_MARKUP,
+ async function (browser, docAcc) {
+ info("Test aria-activedescendant content attribute");
+ await basicListboxTest(browser, false);
+
+ await changeARIAActiveDescendantInvalid(
+ browser,
+ "listbox",
+ "invalid",
+ "item1"
+ );
+
+ await changeARIAActiveDescendant(browser, "listbox", "roaming");
+ await moveARIAActiveDescendantID(browser, "roaming2", "roaming");
+ await changeARIAActiveDescendantInvalid(
+ browser,
+ "listbox",
+ "roaming3",
+ "roaming"
+ );
+ await moveARIAActiveDescendantID(browser, "roaming", "roaming3");
+ },
+ { topLevel: true, chrome: true }
+);
+
+addAccessibleTask(
+ LISTBOX_MARKUP,
+ async function (browser, docAcc) {
+ info("Test ariaActiveDescendantElement element reflection");
+ await basicListboxTest(browser, true);
+ },
+ { topLevel: true, chrome: true }
+);
+
+addAccessibleTask(
+ `
+<input id="activedesc_nondesc_input" aria-activedescendant="activedesc_nondesc_option">
+<div role="listbox">
+ <div role="option" id="activedesc_nondesc_option">option</div>
+</div>`,
+ async function (browser, docAcc) {
+ info("Test aria-activedescendant non-descendant");
+ await synthFocus(
+ browser,
+ "activedesc_nondesc_input",
+ "activedesc_nondesc_option"
+ );
+ },
+ { topLevel: true, chrome: true }
+);
+
+addAccessibleTask(
+ `
+<div id="shadow"></div>
+<script>
+ let host = document.getElementById("shadow");
+ let shadow = host.attachShadow({mode: "open"});
+ let listbox = document.createElement("div");
+ listbox.id = "shadowListbox";
+ listbox.setAttribute("role", "listbox");
+ listbox.setAttribute("tabindex", "0");
+ shadow.appendChild(listbox);
+ let item = document.createElement("div");
+ item.id = "shadowItem1";
+ item.setAttribute("role", "option");
+ listbox.appendChild(item);
+ listbox.setAttribute("aria-activedescendant", "shadowItem1");
+ item = document.createElement("div");
+ item.id = "shadowItem2";
+ item.setAttribute("role", "option");
+ listbox.appendChild(item);
+</script>`,
+ async function (browser, docAcc) {
+ info("Test aria-activedescendant in shadow root");
+ // We want to retrieve elements using their IDs inside the shadow root, so
+ // we define a custom get element by ID method that our utility functions
+ // above call into if it exists.
+ await invokeContentTask(browser, [], () => {
+ content.document._testGetElementById = id =>
+ content.document.getElementById("shadow").shadowRoot.getElementById(id);
+ });
+
+ await synthFocus(browser, "shadowListbox", "shadowItem1");
+ await changeARIAActiveDescendant(
+ browser,
+ "shadowListbox",
+ "shadowItem2",
+ "shadowItem1"
+ );
+ info("Do it again with element reflection");
+ await changeARIAActiveDescendant(
+ browser,
+ "shadowListbox",
+ "shadowItem1",
+ "shadowItem2",
+ true
+ );
+ },
+ { topLevel: true, chrome: true }
+);
+
+addAccessibleTask(
+ `
+<div id="comboboxWithHiddenList" tabindex="0" role="combobox" aria-owns="hiddenList">
+</div>
+<div id="hiddenList" hidden role="listbox">
+ <div id="hiddenListOption" role="option"></div>
+</div>`,
+ async function (browser, docAcc) {
+ info("Test simultaneous insertion, relocation and aria-activedescendant");
+ await synthFocus(
+ browser,
+ "comboboxWithHiddenList",
+ "comboboxWithHiddenList"
+ );
+
+ testStates(
+ findAccessibleChildByID(docAcc, "comboboxWithHiddenList"),
+ STATE_FOCUSED
+ );
+ let evtProm = Promise.all([
+ waitForEvent(EVENT_FOCUS, "hiddenListOption"),
+ waitForStateChange("hiddenListOption", EXT_STATE_ACTIVE, true, true),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ info("hiddenList is owned, so unhiding causes insertion and relocation.");
+ (
+ content.document._testGetElementById || content.document.getElementById
+ ).bind(content.document)("hiddenList").hidden = false;
+ content.document
+ .getElementById("comboboxWithHiddenList")
+ .setAttribute("aria-activedescendant", "hiddenListOption");
+ });
+ await evtProm;
+ testStates(
+ findAccessibleChildByID(docAcc, "hiddenListOption"),
+ STATE_FOCUSED
+ );
+ },
+ { topLevel: true, chrome: true }
+);
+
+addAccessibleTask(
+ `
+<custom-listbox id="custom-listbox1">
+ <div role="listitem" id="l1_1"></div>
+ <div role="listitem" id="l1_2"></div>
+ <div role="listitem" id="l1_3"></div>
+</custom-listbox>
+
+<custom-listbox id="custom-listbox2" aria-activedescendant="l2_1">
+ <div role="listitem" id="l2_1"></div>
+ <div role="listitem" id="l2_2"></div>
+ <div role="listitem" id="l2_3"></div>
+</custom-listbox>
+
+<script>
+customElements.define("custom-listbox",
+ class extends HTMLElement {
+ constructor() {
+ super();
+ this.tabIndex = "0"
+ this._internals = this.attachInternals();
+ this._internals.role = "listbox";
+ this._internals.ariaActiveDescendantElement = this.lastElementChild;
+ }
+ get internals() {
+ return this._internals;
+ }
+ }
+);
+</script>`,
+ async function (browser, docAcc) {
+ await synthFocus(browser, "custom-listbox1", "l1_3");
+
+ let evtProm = Promise.all([
+ waitForEvent(EVENT_FOCUS, "l1_2"),
+ waitForStateChange("l1_3", EXT_STATE_ACTIVE, false, true),
+ waitForStateChange("l1_2", EXT_STATE_ACTIVE, true, true),
+ ]);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById(
+ "custom-listbox1"
+ ).internals.ariaActiveDescendantElement =
+ content.document.getElementById("l1_2");
+ });
+
+ await evtProm;
+
+ evtProm = Promise.all([
+ waitForEvent(EVENT_FOCUS, "custom-listbox1"),
+ waitForStateChange("l1_2", EXT_STATE_ACTIVE, false, true),
+ ]);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById(
+ "custom-listbox1"
+ ).internals.ariaActiveDescendantElement = null;
+ });
+
+ await evtProm;
+
+ await synthFocus(browser, "custom-listbox2", "l2_1");
+ await clearARIAActiveDescendant(browser, "custom-listbox2", "l2_1", "l2_3");
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js
index 139015061f..7a1f90ec13 100644
--- a/accessible/tests/browser/e10s/browser_caching_attributes.js
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -733,7 +733,7 @@ addAccessibleTask(
*/
addAccessibleTask(
`<div id="popover" popover>popover</div>`,
- async function testIspopup(browser, docAcc) {
+ async function testIspopup(browser) {
info("Showing popover");
let shown = waitForEvent(EVENT_SHOW, "popover");
await invokeContentTask(browser, [], () => {
diff --git a/accessible/tests/browser/e10s/browser_caching_large_update.js b/accessible/tests/browser/e10s/browser_caching_large_update.js
index ccf8a86921..9a36ac7326 100644
--- a/accessible/tests/browser/e10s/browser_caching_large_update.js
+++ b/accessible/tests/browser/e10s/browser_caching_large_update.js
@@ -8,59 +8,56 @@
* Test a large update which adds many thousands of Accessibles with a
* lot of content in each.
*/
-addAccessibleTask(
- `<main id="main" hidden></main>`,
- async function (browser, docAcc) {
- let shown = waitForEvent(EVENT_SHOW, "main");
- await invokeContentTask(browser, [], () => {
- // Make a long string.
- let text = "";
- for (let i = 0; i < 100; ++i) {
- text += "a";
- }
- // Create lots of nodes which include the long string.
- const contMain = content.document.getElementById("main");
- // 15000 children of main.
- for (let w = 0; w < 15000; ++w) {
- // Each of those goes 9 deep.
- let parent = contMain;
- for (let d = 0; d < 10; ++d) {
- const div = content.document.createElement("div");
- div.setAttribute("aria-label", `${w} ${d} ${text}`);
- parent.append(div);
- parent = div;
- }
- }
- contMain.hidden = false;
- });
- const main = (await shown).accessible;
- is(main.childCount, 15000, "main has correct number of children");
-
- // We don't want to output passes for every check, since that would output
- // hundreds of thousands of lines, which slows the test to a crawl. Instead,
- // output any failures and keep track of overall success/failure.
- let treeOk = true;
- function check(val, msg) {
- if (!val) {
- ok(false, msg);
- treeOk = false;
- }
+addAccessibleTask(`<main id="main" hidden></main>`, async function (browser) {
+ let shown = waitForEvent(EVENT_SHOW, "main");
+ await invokeContentTask(browser, [], () => {
+ // Make a long string.
+ let text = "";
+ for (let i = 0; i < 100; ++i) {
+ text += "a";
}
-
- info("Checking tree");
+ // Create lots of nodes which include the long string.
+ const contMain = content.document.getElementById("main");
+ // 15000 children of main.
for (let w = 0; w < 15000; ++w) {
- let acc = main.getChildAt(w);
- let parent = main;
+ // Each of those goes 9 deep.
+ let parent = contMain;
for (let d = 0; d < 10; ++d) {
- check(acc, `Got child ${w} depth ${d}`);
- const name = `${w} ${d}`;
- check(acc.name.startsWith(name + " "), `${name}: correct name`);
- check(acc.parent == parent, `${name}: correct parent`);
- parent = acc;
- acc = acc.firstChild;
+ const div = content.document.createElement("div");
+ div.setAttribute("aria-label", `${w} ${d} ${text}`);
+ parent.append(div);
+ parent = div;
}
}
- // check() sets treeOk to false for any failure.
- ok(treeOk, "Tree is correct");
+ contMain.hidden = false;
+ });
+ const main = (await shown).accessible;
+ is(main.childCount, 15000, "main has correct number of children");
+
+ // We don't want to output passes for every check, since that would output
+ // hundreds of thousands of lines, which slows the test to a crawl. Instead,
+ // output any failures and keep track of overall success/failure.
+ let treeOk = true;
+ function check(val, msg) {
+ if (!val) {
+ ok(false, msg);
+ treeOk = false;
+ }
+ }
+
+ info("Checking tree");
+ for (let w = 0; w < 15000; ++w) {
+ let acc = main.getChildAt(w);
+ let parent = main;
+ for (let d = 0; d < 10; ++d) {
+ check(acc, `Got child ${w} depth ${d}`);
+ const name = `${w} ${d}`;
+ check(acc.name.startsWith(name + " "), `${name}: correct name`);
+ check(acc.parent == parent, `${name}: correct parent`);
+ parent = acc;
+ acc = acc.firstChild;
+ }
}
-);
+ // check() sets treeOk to false for any failure.
+ ok(treeOk, "Tree is correct");
+});
diff --git a/accessible/tests/browser/e10s/browser_caching_name.js b/accessible/tests/browser/e10s/browser_caching_name.js
index 55f506b85a..383d268d7d 100644
--- a/accessible/tests/browser/e10s/browser_caching_name.js
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -477,7 +477,7 @@ markupTests.forEach(({ id, ruleset, markup, expected }) =>
markup,
async function (browser, accDoc) {
const observer = {
- observe(subject, topic, data) {
+ observe(subject) {
const event = subject.QueryInterface(nsIAccessibleEvent);
console.log(eventToString(event));
},
diff --git a/accessible/tests/browser/e10s/browser_caching_relations_002.js b/accessible/tests/browser/e10s/browser_caching_relations_002.js
index 072656eb5e..61d92ba4ac 100644
--- a/accessible/tests/browser/e10s/browser_caching_relations_002.js
+++ b/accessible/tests/browser/e10s/browser_caching_relations_002.js
@@ -293,7 +293,7 @@ addAccessibleTask(
);
/**
- * Test details relations on popovers and their invokers.
+ * Test details relations for the popovertarget content attribute.
*/
addAccessibleTask(
`
@@ -304,7 +304,7 @@ addAccessibleTask(
<div id="popover" popover>popover</div>
<div id="details">details</div>
`,
- async function testPopover(browser, docAcc) {
+ async function testPopoverContent(browser, docAcc) {
// The popover is hidden, so nothing should be referring to it.
const hide = findAccessibleChildByID(docAcc, "hide");
await testCachedRelation(hide, RELATION_DETAILS, []);
@@ -330,7 +330,7 @@ addAccessibleTask(
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
- info("Setting toggle2 popovertargetaction");
+ info("Setting toggle2 popovertarget");
await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, toggle2]);
@@ -364,3 +364,106 @@ addAccessibleTask(
},
{ chrome: false, topLevel: true }
);
+
+/**
+ * Test details relations for the popoverTargetElement WebIDL attribute.
+ */
+addAccessibleTask(
+ `
+<button id="toggle1">toggle1</button>
+<button id="toggle2">toggle2</button>
+between
+<div id="popover1" popover>popover1</div>
+<button id="toggle3">toggle3</button>
+<div id="shadowHost"><template shadowrootmode="open">
+ <button id="toggle4">toggle4</button>
+ between
+ <div id="popover2" popover>popover2</div>
+ <button id="toggle5">toggle5</button>
+</template></div>
+<script>
+ const toggle1 = document.getElementById("toggle1");
+ const popover1 = document.getElementById("popover1");
+ toggle1.popoverTargetElement = popover1;
+ const toggle3 = document.getElementById("toggle3");
+ const shadow = document.getElementById("shadowHost").shadowRoot;
+ const toggle4 = shadow.getElementById("toggle4");
+ const popover2 = shadow.getElementById("popover2");
+ toggle3.popoverTargetElement = popover2;
+ toggle4.popoverTargetElement = popover2;
+ const toggle5 = shadow.getElementById("toggle5");
+ toggle5.popoverTargetElement = popover1;
+</script>
+ `,
+ async function testPopoverIdl(browser, docAcc) {
+ // No popover is showing, so there shouldn't be any details relations.
+ const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
+ await testCachedRelation(toggle1, RELATION_DETAILS, []);
+ const toggle2 = findAccessibleChildByID(docAcc, "toggle2");
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ const toggle3 = findAccessibleChildByID(docAcc, "toggle3");
+ await testCachedRelation(toggle3, RELATION_DETAILS, []);
+ const toggle4 = findAccessibleChildByID(docAcc, "toggle4");
+ await testCachedRelation(toggle4, RELATION_DETAILS, []);
+ const toggle5 = findAccessibleChildByID(docAcc, "toggle5");
+ await testCachedRelation(toggle5, RELATION_DETAILS, []);
+
+ info("Showing popover1");
+ let shown = waitForEvent(EVENT_SHOW, "popover1");
+ toggle1.doAction(0);
+ const popover1 = (await shown).accessible;
+ await testCachedRelation(toggle1, RELATION_DETAILS, popover1);
+ // toggle5 is inside the shadow DOM and popover1 is outside, so the target
+ // is valid.
+ await testCachedRelation(toggle5, RELATION_DETAILS, popover1);
+ await testCachedRelation(popover1, RELATION_DETAILS_FOR, [
+ toggle1,
+ toggle5,
+ ]);
+ info("Setting toggle2's popover target to popover1");
+ await invokeContentTask(browser, [], () => {
+ const toggle2Dom = content.document.getElementById("toggle2");
+ const popover1Dom = content.document.getElementById("popover1");
+ toggle2Dom.popoverTargetElement = popover1Dom;
+ });
+ await testCachedRelation(toggle2, RELATION_DETAILS, popover1);
+ await testCachedRelation(popover1, RELATION_DETAILS_FOR, [
+ toggle1,
+ toggle2,
+ toggle5,
+ ]);
+ info("Clearing toggle2's popover target");
+ await invokeContentTask(browser, [], () => {
+ const toggle2Dom = content.document.getElementById("toggle2");
+ toggle2Dom.popoverTargetElement = null;
+ });
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ await testCachedRelation(popover1, RELATION_DETAILS_FOR, [
+ toggle1,
+ toggle5,
+ ]);
+ info("Hiding popover1");
+ let hidden = waitForEvent(EVENT_HIDE, popover1);
+ toggle1.doAction(0);
+ await hidden;
+ await testCachedRelation(toggle1, RELATION_DETAILS, []);
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ await testCachedRelation(toggle5, RELATION_DETAILS, []);
+
+ info("Showing popover2");
+ shown = waitForEvent(EVENT_SHOW, "popover2");
+ toggle4.doAction(0);
+ const popover2 = (await shown).accessible;
+ // toggle4 is in the same shadow DOM as popover2.
+ await testCachedRelation(toggle4, RELATION_DETAILS, popover2);
+ // toggle3 is outside popover2's shadow DOM, so the target isn't valid.
+ await testCachedRelation(toggle3, RELATION_DETAILS, []);
+ await testCachedRelation(popover2, RELATION_DETAILS_FOR, [toggle4]);
+ info("Hiding popover2");
+ hidden = waitForEvent(EVENT_HIDE, popover2);
+ toggle4.doAction(0);
+ await hidden;
+ await testCachedRelation(toggle4, RELATION_DETAILS, []);
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js
index 37f8c46966..7292228f25 100644
--- a/accessible/tests/browser/e10s/browser_caching_states.js
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
+requestLongerTimeout(2);
/* import-globals-from ../../mochitest/role.js */
/* import-globals-from ../../mochitest/states.js */
@@ -484,7 +485,7 @@ addAccessibleTask(
);
/**
- * Test caching of the expanded state for popover target element.
+ * Test caching of the expanded state for the popovertarget content attribute.
*/
addAccessibleTask(
`
@@ -550,3 +551,157 @@ addAccessibleTask(
},
{ chrome: true, topLevel: true, remoteIframe: true }
);
+
+/**
+ * Test caching of the expanded state for the popoverTargetElement WebIDL
+ * attribute.
+ */
+addAccessibleTask(
+ `
+<button id="toggle1">toggle</button>
+<div id="popover1" popover>popover1</div>
+<button id="toggle2">toggle2</button>
+<button id="toggle3">toggle3</button>
+<div id="shadowHost"><template shadowrootmode="open">
+ <button id="toggle4">toggle4</button>
+ <div id="popover2" popover>popover2</div>
+ <button id="toggle5">toggle5</button>
+</template></div>
+<script>
+ const toggle1 = document.getElementById("toggle1");
+ const popover1 = document.getElementById("popover1");
+ toggle1.popoverTargetElement = popover1;
+ const toggle3 = document.getElementById("toggle3");
+ const shadow = document.getElementById("shadowHost").shadowRoot;
+ const toggle4 = shadow.getElementById("toggle4");
+ const popover2 = shadow.getElementById("popover2");
+ toggle3.popoverTargetElement = popover2;
+ toggle4.popoverTargetElement = popover2;
+ const toggle5 = shadow.getElementById("toggle5");
+ toggle5.popoverTargetElement = popover1;
+</script>
+ `,
+ async function (browser, docAcc) {
+ const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
+ // toggle1's popover target is set and connected to the document.
+ testStates(toggle1, STATE_COLLAPSED);
+
+ const toggle2 = findAccessibleChildByID(docAcc, "toggle2");
+ // toggle2's popover target isn't set yet.
+ testStates(
+ toggle2,
+ 0,
+ 0,
+ STATE_EXPANDED | STATE_COLLAPSED,
+ EXT_STATE_EXPANDABLE
+ );
+ info("Setting toggle2's popoverTargetElement");
+ let changed = waitForStateChange(toggle2, EXT_STATE_EXPANDABLE, true, true);
+ await invokeContentTask(browser, [], () => {
+ const toggle2Dom = content.document.getElementById("toggle2");
+ const popover1 = content.document.getElementById("popover1");
+ toggle2Dom.popoverTargetElement = popover1;
+ });
+ await changed;
+ testStates(toggle2, STATE_COLLAPSED);
+
+ const toggle5 = findAccessibleChildByID(docAcc, "toggle5");
+ // toggle5 is inside the shadow DOM and popover1 is outside, so the target
+ // is valid.
+ testStates(toggle5, STATE_COLLAPSED);
+
+ // Changes to the popover should fire events on all invokers.
+ const changeEvents = [
+ [EVENT_STATE_CHANGE, toggle1],
+ [EVENT_STATE_CHANGE, toggle2],
+ [EVENT_STATE_CHANGE, toggle5],
+ ];
+ info("Showing popover1");
+ changed = waitForEvents(changeEvents);
+ toggle1.doAction(0);
+ await changed;
+ testStates(toggle1, STATE_EXPANDED);
+ testStates(toggle2, STATE_EXPANDED);
+
+ info("Hiding popover1");
+ changed = waitForEvents(changeEvents);
+ toggle1.doAction(0);
+ await changed;
+ testStates(toggle1, STATE_COLLAPSED);
+ testStates(toggle2, STATE_COLLAPSED);
+
+ info("Clearing toggle1's popover target");
+ changed = waitForStateChange(toggle1, EXT_STATE_EXPANDABLE, false, true);
+ await invokeContentTask(browser, [], () => {
+ const toggle1Dom = content.document.getElementById("toggle1");
+ toggle1Dom.popoverTargetElement = null;
+ });
+ await changed;
+ testStates(
+ toggle1,
+ 0,
+ 0,
+ STATE_EXPANDED | STATE_COLLAPSED,
+ EXT_STATE_EXPANDABLE
+ );
+
+ info("Setting toggle2's popover target to a disconnected node");
+ changed = waitForStateChange(toggle2, EXT_STATE_EXPANDABLE, false, true);
+ await invokeContentTask(browser, [], () => {
+ const toggle2Dom = content.document.getElementById("toggle2");
+ const popover3 = content.document.createElement("div");
+ popover3.popover = "auto";
+ popover3.textContent = "popover3";
+ // We don't append popover3 anywhere, so it is disconnected.
+ toggle2Dom.popoverTargetElement = popover3;
+ });
+ await changed;
+ testStates(
+ toggle2,
+ 0,
+ 0,
+ STATE_EXPANDED | STATE_COLLAPSED,
+ EXT_STATE_EXPANDABLE
+ );
+
+ const toggle3 = findAccessibleChildByID(docAcc, "toggle3");
+ // toggle3 is outside popover2's shadow DOM, so the target isn't valid.
+ testStates(
+ toggle3,
+ 0,
+ 0,
+ STATE_EXPANDED | STATE_COLLAPSED,
+ EXT_STATE_EXPANDABLE
+ );
+ const toggle4 = findAccessibleChildByID(docAcc, "toggle4");
+ // toggle4 is in the same shadow DOM as popover2.
+ testStates(toggle4, STATE_COLLAPSED);
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the mixed state of indeterminate HTML checkboxes.
+ */
+addAccessibleTask(
+ `<input type="checkbox" id="checkbox">`,
+ async function testHTMLCheckboxMixed(browser, docAcc) {
+ const checkbox = findAccessibleChildByID(docAcc, "checkbox");
+ testStates(checkbox, 0, 0, STATE_MIXED);
+ info("Setting indeterminate on checkbox");
+ let changed = waitForStateChange(checkbox, STATE_MIXED, true);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("checkbox").indeterminate = true;
+ });
+ await changed;
+ testStates(checkbox, STATE_MIXED);
+ info("Clearing indeterminate on checkbox");
+ changed = waitForStateChange(checkbox, STATE_MIXED, false);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("checkbox").indeterminate = false;
+ });
+ await changed;
+ testStates(checkbox, 0, 0, STATE_MIXED);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_table.js b/accessible/tests/browser/e10s/browser_caching_table.js
index 9c8bcb9616..0329e6411b 100644
--- a/accessible/tests/browser/e10s/browser_caching_table.js
+++ b/accessible/tests/browser/e10s/browser_caching_table.js
@@ -482,7 +482,7 @@ addAccessibleTask(
*/
addAccessibleTask(
`<table><tr id="tr"></tr></table>`,
- async function (browser, docAcc) {
+ async function (browser) {
let reordered = waitForEvent(EVENT_REORDER, "tr");
await invokeContentTask(browser, [], () => {
const iframe = content.document.createElement("iframe");
diff --git a/accessible/tests/browser/e10s/browser_caching_text_bounds.js b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
index 3e37bf7490..486e28df53 100644
--- a/accessible/tests/browser/e10s/browser_caching_text_bounds.js
+++ b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
@@ -138,9 +138,13 @@ async function testLineWithNonRenderedSpace(docAcc, browser, id, length) {
const w = {};
const h = {};
acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE);
- ok(x.value > prevX, `${id}: offset ${offset} x is larger (${x.value})`);
+ Assert.greater(
+ x.value,
+ prevX,
+ `${id}: offset ${offset} x is larger (${x.value})`
+ );
prevX = x.value;
- ok(w.value > 0, `${id}: offset ${offset} width > 0`);
+ Assert.greater(w.value, 0, `${id}: offset ${offset} width > 0`);
}
}
@@ -566,7 +570,11 @@ c</textarea>
{},
COORDTYPE_SCREEN_RELATIVE
);
- ok(newY.value < oldY.value, "y coordinate smaller after scrolling down");
+ Assert.less(
+ newY.value,
+ oldY.value,
+ "y coordinate smaller after scrolling down"
+ );
},
{ chrome: true, topLevel: true, iframe: !true }
);
diff --git a/accessible/tests/browser/e10s/browser_file_input.js b/accessible/tests/browser/e10s/browser_file_input.js
index 238e48740e..4c68e8e6da 100644
--- a/accessible/tests/browser/e10s/browser_file_input.js
+++ b/accessible/tests/browser/e10s/browser_file_input.js
@@ -34,7 +34,7 @@ addAccessibleTask(
function chooseFile(id) {
return invokeContentTask(browser, [id], contentId => {
const MockFilePicker = content.SpecialPowers.MockFilePicker;
- MockFilePicker.init(content);
+ MockFilePicker.init(content.browsingContext);
MockFilePicker.useBlobFile();
MockFilePicker.returnValue = MockFilePicker.returnOK;
const input = content.document.getElementById(contentId);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
index a82fc4c04d..8ccbe58751 100644
--- a/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
+++ b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
@@ -24,7 +24,7 @@ const snippet = `
addAccessibleTask(
snippet,
- async function (browser, accDoc) {
+ async function (browser) {
await invokeFocus(browser, "select");
// Expand the select. A dropdown item should get focus.
// Note that the dropdown is rendered in the parent process.
diff --git a/accessible/tests/browser/events/browser_test_docload.js b/accessible/tests/browser/events/browser_test_docload.js
index 78ac77fd8c..12076b3002 100644
--- a/accessible/tests/browser/events/browser_test_docload.js
+++ b/accessible/tests/browser/events/browser_test_docload.js
@@ -30,7 +30,7 @@ function urlChecker(url) {
};
}
-async function runTests(browser, accDoc) {
+async function runTests(browser) {
let onLoadEvents = waitForEvents({
expected: [
[EVENT_REORDER, getAccessible(browser)],
diff --git a/accessible/tests/browser/events/browser_test_focus_browserui.js b/accessible/tests/browser/events/browser_test_focus_browserui.js
index 969d336c74..2f67cb3681 100644
--- a/accessible/tests/browser/events/browser_test_focus_browserui.js
+++ b/accessible/tests/browser/events/browser_test_focus_browserui.js
@@ -11,7 +11,7 @@ loadScripts(
{ name: "role.js", dir: MOCHITESTS_DIR }
);
-async function runTests(browser, accDoc) {
+async function runTests(browser) {
await SpecialPowers.pushPrefEnv({
// If Fission is disabled, the pref is no-op.
set: [["fission.bfcacheInParent", true]],
diff --git a/accessible/tests/browser/events/browser_test_focus_dialog.js b/accessible/tests/browser/events/browser_test_focus_dialog.js
index 71485a678d..03a1b82dc1 100644
--- a/accessible/tests/browser/events/browser_test_focus_dialog.js
+++ b/accessible/tests/browser/events/browser_test_focus_dialog.js
@@ -11,7 +11,7 @@ loadScripts(
{ name: "role.js", dir: MOCHITESTS_DIR }
);
-async function runTests(browser, accDoc) {
+async function runTests(browser) {
let onFocus = waitForEvent(EVENT_FOCUS, "button");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("button").focus();
diff --git a/accessible/tests/browser/events/browser_test_focus_urlbar.js b/accessible/tests/browser/events/browser_test_focus_urlbar.js
index 68b2b07f3c..647b837e9f 100644
--- a/accessible/tests/browser/events/browser_test_focus_urlbar.js
+++ b/accessible/tests/browser/events/browser_test_focus_urlbar.js
@@ -68,10 +68,10 @@ class TipTestProvider extends UrlbarProvider {
get type() {
return UrlbarUtils.PROVIDER_TYPE.PROFILE;
}
- isActive(context) {
+ isActive() {
return true;
}
- isRestricting(context) {
+ isRestricting() {
return true;
}
async startQuery(context, addCallback) {
diff --git a/accessible/tests/browser/events/browser_test_scrolling.js b/accessible/tests/browser/events/browser_test_scrolling.js
index 9678ee767b..d9425721bf 100644
--- a/accessible/tests/browser/events/browser_test_scrolling.js
+++ b/accessible/tests/browser/events/browser_test_scrolling.js
@@ -25,13 +25,15 @@ c</textarea>
});
let [scrollEvent1, scrollEndEvent1] = await onScrolling;
scrollEvent1.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEvent1.maxScrollY >= scrollEvent1.scrollY,
+ Assert.greaterOrEqual(
+ scrollEvent1.maxScrollY,
+ scrollEvent1.scrollY,
"scrollY is within max"
);
scrollEndEvent1.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEndEvent1.maxScrollY >= scrollEndEvent1.scrollY,
+ Assert.greaterOrEqual(
+ scrollEndEvent1.maxScrollY,
+ scrollEndEvent1.scrollY,
"scrollY is within max"
);
@@ -44,13 +46,15 @@ c</textarea>
});
let [scrollEvent2, scrollEndEvent2] = await onScrolling;
scrollEvent2.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEvent2.scrollY > scrollEvent1.scrollY,
+ Assert.greater(
+ scrollEvent2.scrollY,
+ scrollEvent1.scrollY,
`${scrollEvent2.scrollY} > ${scrollEvent1.scrollY}`
);
scrollEndEvent2.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEndEvent2.maxScrollY >= scrollEndEvent2.scrollY,
+ Assert.greaterOrEqual(
+ scrollEndEvent2.maxScrollY,
+ scrollEndEvent2.scrollY,
"scrollY is within max"
);
@@ -63,17 +67,20 @@ c</textarea>
});
let [scrollEvent3, scrollEndEvent3] = await onScrolling;
scrollEvent3.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEvent3.maxScrollX >= scrollEvent3.scrollX,
+ Assert.greaterOrEqual(
+ scrollEvent3.maxScrollX,
+ scrollEvent3.scrollX,
"scrollX is within max"
);
scrollEndEvent3.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEndEvent3.maxScrollX >= scrollEndEvent3.scrollX,
+ Assert.greaterOrEqual(
+ scrollEndEvent3.maxScrollX,
+ scrollEndEvent3.scrollX,
"scrollY is within max"
);
- ok(
- scrollEvent3.scrollX > scrollEvent2.scrollX,
+ Assert.greater(
+ scrollEvent3.scrollX,
+ scrollEvent2.scrollX,
`${scrollEvent3.scrollX} > ${scrollEvent2.scrollX}`
);
@@ -87,13 +94,15 @@ c</textarea>
});
let [scrollEvent4, scrollEndEvent4] = await onScrolling;
scrollEvent4.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEvent4.maxScrollY >= scrollEvent4.scrollY,
+ Assert.greaterOrEqual(
+ scrollEvent4.maxScrollY,
+ scrollEvent4.scrollY,
"scrollY is within max"
);
scrollEndEvent4.QueryInterface(nsIAccessibleScrollingEvent);
- ok(
- scrollEndEvent4.maxScrollY >= scrollEndEvent4.scrollY,
+ Assert.greaterOrEqual(
+ scrollEndEvent4.maxScrollY,
+ scrollEndEvent4.scrollY,
"scrollY is within max"
);
@@ -119,7 +128,7 @@ addAccessibleTask(
<p>b</p>
<p id="c">c</p>
`,
- async function (browser, accDoc) {
+ async function (browser) {
let onScrollingStart = waitForEvent(EVENT_SCROLLING_START, "c");
await SpecialPowers.spawn(browser, [], () => {
content.location.hash = "#c";
@@ -137,7 +146,7 @@ addAccessibleTask(
<h1 style="height: 300%;" id="inside-scrollable">test</h1>
</div>
`,
- async function (browser, accDoc) {
+ async function (browser) {
let onScrollingStart = waitForEvent(
EVENT_SCROLLING_START,
"inside-scrollable"
diff --git a/accessible/tests/browser/mac/browser_app.js b/accessible/tests/browser/mac/browser_app.js
index bedefae440..e7e18b5ddd 100644
--- a/accessible/tests/browser/mac/browser_app.js
+++ b/accessible/tests/browser/mac/browser_app.js
@@ -138,7 +138,7 @@ add_task(async () => {
gBrowser,
url: "about:license",
},
- async browser => {
+ async () => {
let root = await getMacAccessible(document);
let rootChildCount = () => root.getAttributeValue("AXChildren").length;
@@ -206,8 +206,10 @@ add_task(async () => {
is(rootChildCount(), baseRootChildCount + 1, "Root has another child");
// Close popup
+ let hide = waitForMacEvent("AXUIElementDestroyed");
EventUtils.synthesizeKey("KEY_Escape");
await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden");
+ await hide;
// We're back to the base child count
is(rootChildCount(), baseRootChildCount, "Root has the base child count");
@@ -225,7 +227,7 @@ add_task(async () => {
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
url: "http://example.com",
},
- async browser => {
+ async () => {
let input = await getMacAccessible("urlbar-input");
is(
input.getAttributeValue("AXValue"),
@@ -238,6 +240,38 @@ add_task(async () => {
});
/**
+ * Tests attributed text in nav bar has no invisible AXAttachments
+ */
+add_task(async () => {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ url: "http://example.com",
+ },
+ async () => {
+ let root = await getMacAccessible(document);
+ let navBar = await getMacAccessible("nav-bar");
+ let elemRange = root.getParameterizedAttributeValue(
+ "AXTextMarkerRangeForUIElement",
+ navBar
+ );
+ let attributedString = root.getParameterizedAttributeValue(
+ "AXAttributedStringForTextMarkerRange",
+ elemRange
+ );
+ let attachmentRoles = attributedString.map(s =>
+ s.AXAttachment ? s.AXAttachment.getAttributeValue("AXRole") : null
+ );
+ ok(
+ !attachmentRoles.includes("AXMenu"),
+ "Collapsed menu should be embedded in attributed text"
+ );
+ }
+ );
+});
+
+/**
* Test context menu
*/
add_task(async () => {
diff --git a/accessible/tests/browser/mac/browser_bounds.js b/accessible/tests/browser/mac/browser_bounds.js
index 09343d7c9d..bc7939cfe0 100644
--- a/accessible/tests/browser/mac/browser_bounds.js
+++ b/accessible/tests/browser/mac/browser_bounds.js
@@ -21,22 +21,26 @@ addAccessibleTask(
// test them here instead of calling AXFrame directly.
const [helloWidth, helloHeight] = hello.getAttributeValue("AXSize");
const [worldWidth, worldHeight] = world.getAttributeValue("AXSize");
- ok(helloWidth > 0, "Hello has a positive width");
- ok(helloHeight > 0, "Hello has a positive height");
- ok(worldWidth > 0, "World has a positive width");
- ok(worldHeight > 0, "World has a positive height");
- ok(helloHeight < worldHeight, "Hello has a smaller height than world");
- ok(helloWidth < worldWidth, "Hello has a smaller width than world");
+ Assert.greater(helloWidth, 0, "Hello has a positive width");
+ Assert.greater(helloHeight, 0, "Hello has a positive height");
+ Assert.greater(worldWidth, 0, "World has a positive width");
+ Assert.greater(worldHeight, 0, "World has a positive height");
+ Assert.less(
+ helloHeight,
+ worldHeight,
+ "Hello has a smaller height than world"
+ );
+ Assert.less(helloWidth, worldWidth, "Hello has a smaller width than world");
// Note: these are mac screen coords, so our origin is bottom left
const [helloX, helloY] = hello.getAttributeValue("AXPosition");
const [worldX, worldY] = world.getAttributeValue("AXPosition");
- ok(helloX > 0, "Hello has a positive X");
- ok(helloY > 0, "Hello has a positive Y");
- ok(worldX > 0, "World has a positive X");
- ok(worldY > 0, "World has a positive Y");
- ok(helloY > worldY, "Hello has a larger Y than world");
- ok(helloX == worldX, "Hello and world have the same X");
+ Assert.greater(helloX, 0, "Hello has a positive X");
+ Assert.greater(helloY, 0, "Hello has a positive Y");
+ Assert.greater(worldX, 0, "World has a positive X");
+ Assert.greater(worldY, 0, "World has a positive Y");
+ Assert.greater(helloY, worldY, "Hello has a larger Y than world");
+ Assert.equal(helloX, worldX, "Hello and world have the same X");
}
);
@@ -57,21 +61,25 @@ addAccessibleTask(
// test them here instead of calling AXFrame directly.
const [helloWidth, helloHeight] = hello.getAttributeValue("AXSize");
const [worldWidth, worldHeight] = world.getAttributeValue("AXSize");
- ok(helloWidth > 0, "Hello has a positive width");
- ok(helloHeight > 0, "Hello has a positive height");
- ok(worldWidth > 0, "World has a positive width");
- ok(worldHeight > 0, "World has a positive height");
- ok(helloHeight < worldHeight, "Hello has a smaller height than world");
- ok(helloWidth < worldWidth, "Hello has a smaller width than world");
+ Assert.greater(helloWidth, 0, "Hello has a positive width");
+ Assert.greater(helloHeight, 0, "Hello has a positive height");
+ Assert.greater(worldWidth, 0, "World has a positive width");
+ Assert.greater(worldHeight, 0, "World has a positive height");
+ Assert.less(
+ helloHeight,
+ worldHeight,
+ "Hello has a smaller height than world"
+ );
+ Assert.less(helloWidth, worldWidth, "Hello has a smaller width than world");
// Note: these are mac screen coords, so our origin is bottom left
const [helloX, helloY] = hello.getAttributeValue("AXPosition");
const [worldX, worldY] = world.getAttributeValue("AXPosition");
- ok(helloX < 0, "Hello has a negative X");
- ok(helloY > 0, "Hello has a positive Y");
- ok(worldX < 0, "World has a negative X");
- ok(worldY > 0, "World has a positive Y");
- ok(helloY > worldY, "Hello has a larger Y than world");
- ok(helloX == worldX, "Hello and world have the same X");
+ Assert.less(helloX, 0, "Hello has a negative X");
+ Assert.greater(helloY, 0, "Hello has a positive Y");
+ Assert.less(worldX, 0, "World has a negative X");
+ Assert.greater(worldY, 0, "World has a positive Y");
+ Assert.greater(helloY, worldY, "Hello has a larger Y than world");
+ Assert.equal(helloX, worldX, "Hello and world have the same X");
}
);
diff --git a/accessible/tests/browser/mac/browser_live_regions.js b/accessible/tests/browser/mac/browser_live_regions.js
index 10a03120f8..aa07f003df 100644
--- a/accessible/tests/browser/mac/browser_live_regions.js
+++ b/accessible/tests/browser/mac/browser_live_regions.js
@@ -131,7 +131,7 @@ addAccessibleTask(
<button id="button" aria-label="Start"></button>
</div>
`,
- async (browser, accDoc) => {
+ async browser => {
let liveRegionChanged = waitForMacEvent("AXLiveRegionChanged", "live");
await SpecialPowers.spawn(browser, [], () => {
content.document.getElementById("time").textContent = "4:56pm";
diff --git a/accessible/tests/browser/mac/browser_menulist.js b/accessible/tests/browser/mac/browser_menulist.js
index b26a0be782..3b0fe8b210 100644
--- a/accessible/tests/browser/mac/browser_menulist.js
+++ b/accessible/tests/browser/mac/browser_menulist.js
@@ -74,7 +74,7 @@ addAccessibleTask(
"First menu item is selected"
);
// focus the second item, and verify it is selected
- event = waitForMacEvent("AXFocusedUIElementChanged", (iface, data) => {
+ event = waitForMacEvent("AXFocusedUIElementChanged", iface => {
try {
return iface.getAttributeValue("AXTitle") == "100%";
} catch (e) {
diff --git a/accessible/tests/browser/mac/browser_roles_elements.js b/accessible/tests/browser/mac/browser_roles_elements.js
index 791598fed6..b6049e7afd 100644
--- a/accessible/tests/browser/mac/browser_roles_elements.js
+++ b/accessible/tests/browser/mac/browser_roles_elements.js
@@ -47,6 +47,7 @@ addAccessibleTask(
<div id="complementary" role="complementary"></div>
<div id="contentinfo" role="contentinfo"></div>
<div id="form" role="form"></div>
+ <div id="form_label" aria-label="form" role="form"></div>
<div id="main" role="main"></div>
<div id="navigation" role="navigation"></div>
<div id="search" role="search"></div>
@@ -149,7 +150,8 @@ addAccessibleTask(
"AXLandmarkComplementary"
);
testRoleAndSubRole(accDoc, "contentinfo", null, "AXLandmarkContentInfo");
- testRoleAndSubRole(accDoc, "form", null, "AXLandmarkForm");
+ testRoleAndSubRole(accDoc, "form", null, "AXApplicationGroup");
+ testRoleAndSubRole(accDoc, "form_label", null, "AXLandmarkForm");
testRoleAndSubRole(accDoc, "main", null, "AXLandmarkMain");
testRoleAndSubRole(accDoc, "navigation", null, "AXLandmarkNavigation");
testRoleAndSubRole(accDoc, "search", null, "AXLandmarkSearch");
diff --git a/accessible/tests/browser/mac/browser_rotor.js b/accessible/tests/browser/mac/browser_rotor.js
index 3f13506757..87ac40592d 100644
--- a/accessible/tests/browser/mac/browser_rotor.js
+++ b/accessible/tests/browser/mac/browser_rotor.js
@@ -269,7 +269,7 @@ addAccessibleTask(
"AXUIElementCountForSearchPredicate",
NSDictionary(searchPred)
);
- is(4, tableCount, "Found four tables");
+ is(tableCount, 3, "Found three tables");
const tables = webArea.getParameterizedAttributeValue(
"AXUIElementsForSearchPredicate",
@@ -278,7 +278,6 @@ addAccessibleTask(
const shapes = getNativeInterface(accDoc, "shapes");
const food = getNativeInterface(accDoc, "food");
const ariaTable = getNativeInterface(accDoc, "ariaTable");
- const grid = getNativeInterface(accDoc, "grid");
is(
shapes.getAttributeValue("AXColumnCount"),
@@ -295,11 +294,6 @@ addAccessibleTask(
tables[2].getAttributeValue("AXColumnCount"),
"Found correct third table"
);
- is(
- grid.getAttributeValue("AXColumnCount"),
- tables[3].getAttributeValue("AXColumnCount"),
- "Found correct fourth table"
- );
}
);
diff --git a/accessible/tests/browser/mac/browser_text_leaf.js b/accessible/tests/browser/mac/browser_text_leaf.js
index 21deed6212..c65e8c6ebe 100644
--- a/accessible/tests/browser/mac/browser_text_leaf.js
+++ b/accessible/tests/browser/mac/browser_text_leaf.js
@@ -77,7 +77,11 @@ addAccessibleTask(
NSRange(3, 8)
);
- ok(smallBounds.size[0] < largeBounds.size[0], "longer range is wider");
+ Assert.less(
+ smallBounds.size[0],
+ largeBounds.size[0],
+ "longer range is wider"
+ );
},
{ chrome: true, iframe: true, remoteIframe: true }
);
diff --git a/accessible/tests/browser/mac/browser_text_selection.js b/accessible/tests/browser/mac/browser_text_selection.js
index a914adba8e..7e2145631c 100644
--- a/accessible/tests/browser/mac/browser_text_selection.js
+++ b/accessible/tests/browser/mac/browser_text_selection.js
@@ -82,7 +82,7 @@ addAccessibleTask(
Hello <a href="#" id="link">World</a>,
I <a href="#" style="user-select: none;" id="unselectable_link">love</a>
<button id="button">you</button></p>`,
- async (browser, accDoc) => {
+ async browser => {
// Set up an AXSelectedTextChanged listener here. It will get resolved
// on the first non-root event it encounters, so if we test its data at the end
// of this test it will show us the first text-selectable object that was focused,
diff --git a/accessible/tests/browser/mac/browser_toggle_radio_check.js b/accessible/tests/browser/mac/browser_toggle_radio_check.js
index 1695d73b0d..f9094ac3d7 100644
--- a/accessible/tests/browser/mac/browser_toggle_radio_check.js
+++ b/accessible/tests/browser/mac/browser_toggle_radio_check.js
@@ -128,7 +128,7 @@ addAccessibleTask(
// Changing from checked to mixed fires two events. Make sure we wait until
// the second so we're asserting based on the latest state.
- evt = waitForMacEvent("AXValueChanged", (iface, data) => {
+ evt = waitForMacEvent("AXValueChanged", iface => {
return (
iface.getAttributeValue("AXDOMIdentifier") == "checkbox" &&
iface.getAttributeValue("AXValue") == 2
diff --git a/accessible/tests/browser/mac/browser_webarea.js b/accessible/tests/browser/mac/browser_webarea.js
index ac6122de14..4872c58845 100644
--- a/accessible/tests/browser/mac/browser_webarea.js
+++ b/accessible/tests/browser/mac/browser_webarea.js
@@ -8,8 +8,8 @@
loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
// Test web area role and AXLoadComplete event
-addAccessibleTask(``, async (browser, accDoc) => {
- let evt = waitForMacEvent("AXLoadComplete", (iface, data) => {
+addAccessibleTask(``, async browser => {
+ let evt = waitForMacEvent("AXLoadComplete", iface => {
return iface.getAttributeValue("AXDescription") == "webarea test";
});
await SpecialPowers.spawn(browser, [], () => {
@@ -29,16 +29,16 @@ addAccessibleTask(``, async (browser, accDoc) => {
});
// Test iframe web area role and AXLayoutComplete event
-addAccessibleTask(`<title>webarea test</title>`, async (browser, accDoc) => {
+addAccessibleTask(`<title>webarea test</title>`, async browser => {
// If the iframe loads before the top level document finishes loading, we'll
// get both an AXLayoutComplete event for the iframe and an AXLoadComplete
// event for the document. Otherwise, if the iframe loads after the
// document, we'll get one AXLoadComplete event.
let eventPromise = Promise.race([
- waitForMacEvent("AXLayoutComplete", (iface, data) => {
+ waitForMacEvent("AXLayoutComplete", iface => {
return iface.getAttributeValue("AXDescription") == "iframe document";
}),
- waitForMacEvent("AXLoadComplete", (iface, data) => {
+ waitForMacEvent("AXLoadComplete", iface => {
return iface.getAttributeValue("AXDescription") == "webarea test";
}),
]);
diff --git a/accessible/tests/browser/scroll/browser_test_scroll_bounds.js b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
index 31de002cda..33a99266db 100644
--- a/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
+++ b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
@@ -238,7 +238,11 @@ addAccessibleTask(
newTopBounds[0],
"x of non-fixed element remains accurate."
);
- ok(newTopBounds[1] < 0, "y coordinate shows item scrolled off page");
+ Assert.less(
+ newTopBounds[1],
+ 0,
+ "y coordinate shows item scrolled off page"
+ );
is(
origTopBounds[2],
newTopBounds[2],
@@ -254,7 +258,11 @@ addAccessibleTask(
newDBounds[0],
"x of non-fixed container element remains accurate."
);
- ok(newDBounds[1] < 0, "y coordinate shows container scrolled off page");
+ Assert.less(
+ newDBounds[1],
+ 0,
+ "y coordinate shows container scrolled off page"
+ );
// Removing the position styling on this acc causes it to be bound by
// its parent's bounding box, which alters its width as a block element.
// We don't particularly care about width in this test, so skip it.
@@ -481,7 +489,7 @@ addAccessibleTask(
newBounds[0],
`x coord of non-sticky element remains accurate.`
);
- ok(newBounds[1] < 0, "y coordinate shows item scrolled off page");
+ Assert.less(newBounds[1], 0, "y coordinate shows item scrolled off page");
// Removing the position styling on this acc causes it to be bound by
// its parent's bounding box, which alters its width as a block element.
diff --git a/accessible/tests/browser/scroll/browser_test_scroll_substring.js b/accessible/tests/browser/scroll/browser_test_scroll_substring.js
index e8426d00ca..031b8a5124 100644
--- a/accessible/tests/browser/scroll/browser_test_scroll_substring.js
+++ b/accessible/tests/browser/scroll/browser_test_scroll_substring.js
@@ -45,8 +45,9 @@ The only thing I found in the fridge was a dead dove in a bag.
text.getCharacterExtents(7, {}, objY, {}, {}, COORDTYPE_SCREEN_RELATIVE);
return objY.value;
};
- ok(
- containerHeight < getCharY(),
+ Assert.less(
+ containerHeight,
+ getCharY(),
"Character is outside of container bounds"
);
text.scrollSubstringTo(7, 8, SCROLL_TYPE_TOP_EDGE);
diff --git a/accessible/tests/browser/selectable/browser_test_select.js b/accessible/tests/browser/selectable/browser_test_select.js
index f86a371d81..6a51fd4f3b 100644
--- a/accessible/tests/browser/selectable/browser_test_select.js
+++ b/accessible/tests/browser/selectable/browser_test_select.js
@@ -313,7 +313,7 @@ addAccessibleTask(
</select>
</form>
`,
- async function (browser, docAcc) {
+ async function (browser) {
let selected = waitForEvent(EVENT_SELECTION_WITHIN, "select");
await invokeContentTask(browser, [], () => {
const form = content.document.getElementById("form");
diff --git a/accessible/tests/browser/shared-head.js b/accessible/tests/browser/shared-head.js
index fe87a77765..c238a5a7a0 100644
--- a/accessible/tests/browser/shared-head.js
+++ b/accessible/tests/browser/shared-head.js
@@ -830,7 +830,7 @@ const CACHE_WAIT_TIMEOUT_MS = 5000;
* be used to record a pass or fail in the test.
*/
function untilCacheCondition(conditionFunc, argsFunc) {
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
let args = argsFunc();
if (conditionFunc(...args)) {
resolve(args);
@@ -838,7 +838,7 @@ function untilCacheCondition(conditionFunc, argsFunc) {
}
let cacheObserver = {
- observe(subject) {
+ observe() {
args = argsFunc();
if (conditionFunc(...args)) {
clearTimeout(this.timer);
@@ -945,7 +945,7 @@ function runPython(code) {
"ws://mochi.test:8888/browser/accessible/tests/browser/python_runner"
);
if (gPythonSocket.readyState != WebSocket.OPEN) {
- gPythonSocket.onopen = evt => {
+ gPythonSocket.onopen = () => {
gPythonSocket.send(code);
gPythonSocket.onopen = null;
};
diff --git a/accessible/tests/browser/text/browser_text_paragraph_boundary.js b/accessible/tests/browser/text/browser_text_paragraph_boundary.js
index 04e64520e8..b206829398 100644
--- a/accessible/tests/browser/text/browser_text_paragraph_boundary.js
+++ b/accessible/tests/browser/text/browser_text_paragraph_boundary.js
@@ -8,7 +8,7 @@
// boundary on an Accessible which has remote ProxyAccessible descendants.
addAccessibleTask(
`test`,
- async function testParagraphBoundaryWithRemoteDescendants(browser, accDoc) {
+ async function testParagraphBoundaryWithRemoteDescendants() {
const root = getRootAccessible(document).QueryInterface(
Ci.nsIAccessibleText
);
diff --git a/accessible/tests/browser/text/head.js b/accessible/tests/browser/text/head.js
index fa4b095892..72195ddbb0 100644
--- a/accessible/tests/browser/text/head.js
+++ b/accessible/tests/browser/text/head.js
@@ -142,7 +142,7 @@ function testBoundarySequence(
// Editable text
async function waitForCopy(browser) {
- await BrowserTestUtils.waitForContentEvent(browser, "copy", false, evt => {
+ await BrowserTestUtils.waitForContentEvent(browser, "copy", false, () => {
return true;
});
diff --git a/accessible/tests/browser/tree/browser_aria_owns.js b/accessible/tests/browser/tree/browser_aria_owns.js
index 0ca55ed357..904597cc66 100644
--- a/accessible/tests/browser/tree/browser_aria_owns.js
+++ b/accessible/tests/browser/tree/browser_aria_owns.js
@@ -176,7 +176,7 @@ addAccessibleTask(
addAccessibleTask(
`
<select id="container" aria-owns="boom" multiple></select>`,
- async function (browser, accDoc) {
+ async function () {
ok(true, "Did not crash");
}
);
diff --git a/accessible/tests/browser/tree/browser_browser_element.js b/accessible/tests/browser/tree/browser_browser_element.js
index 82be24d93c..d6ece6676e 100644
--- a/accessible/tests/browser/tree/browser_browser_element.js
+++ b/accessible/tests/browser/tree/browser_browser_element.js
@@ -8,7 +8,7 @@ loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
// Test that the tree is correct for browser elements containing remote
// documents.
-addAccessibleTask(`test`, async function (browser, docAcc) {
+addAccessibleTask(`test`, async function (browser) {
// testAccessibleTree also verifies childCount, indexInParent and parent.
testAccessibleTree(browser, {
INTERNAL_FRAME: [{ DOCUMENT: [{ TEXT_LEAF: [] }] }],
diff --git a/accessible/tests/browser/tree/browser_lazy_tabs.js b/accessible/tests/browser/tree/browser_lazy_tabs.js
index f7aa9bdeb2..46e10d0bac 100644
--- a/accessible/tests/browser/tree/browser_lazy_tabs.js
+++ b/accessible/tests/browser/tree/browser_lazy_tabs.js
@@ -5,7 +5,7 @@
// Test that lazy background tabs aren't unintentionally loaded when building
// the a11y tree (bug 1700708).
-addAccessibleTask(``, async function (browser, accDoc) {
+addAccessibleTask(``, async function () {
await SpecialPowers.pushPrefEnv({
set: [["browser.sessionstore.restore_on_demand", true]],
});
diff --git a/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js b/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js
index 623f2640f0..e6b55d0fd3 100644
--- a/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js
+++ b/accessible/tests/browser/tree/browser_test_nsIAccessibleDocument_URL.js
@@ -25,7 +25,7 @@ async function promiseEventDocumentLoadComplete(expectedURL) {
add_task(async function testInDataURI() {
const kURL = "data:text/html,Some text";
const waitForDocumentLoadComplete = promiseEventDocumentLoadComplete("");
- await BrowserTestUtils.withNewTab(kURL, async browser => {
+ await BrowserTestUtils.withNewTab(kURL, async () => {
is(
(await waitForDocumentLoadComplete).URL,
"",
@@ -44,7 +44,7 @@ add_task(async function testInHTTPSURIContainingPrivateThings() {
"https://example.com/browser/toolkit/content/tests/browser/file_empty.html?query=some#ref";
const waitForDocumentLoadComplete =
promiseEventDocumentLoadComplete(kURLWithoutUserPass);
- await BrowserTestUtils.withNewTab(kURL, async browser => {
+ await BrowserTestUtils.withNewTab(kURL, async () => {
is(
(await waitForDocumentLoadComplete).URL,
kURLWithoutUserPass,
diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py
index d6dc19f0fb..726eea07a4 100644
--- a/accessible/tests/browser/windows/a11y_setup.py
+++ b/accessible/tests/browser/windows/a11y_setup.py
@@ -9,19 +9,28 @@ import ctypes
import os
from ctypes import POINTER, byref
from ctypes.wintypes import BOOL, HWND, LPARAM, POINT # noqa: F401
+from dataclasses import dataclass
+import comtypes.automation
import comtypes.client
import psutil
from comtypes import COMError, IServiceProvider
+CHILDID_SELF = 0
+COWAIT_DEFAULT = 0
+EVENT_OBJECT_FOCUS = 0x8005
+GA_ROOT = 2
+NAVRELATION_EMBEDS = 0x1009
+OBJID_CLIENT = -4
+RPC_S_CALLPENDING = -2147417835
+WINEVENT_OUTOFCONTEXT = 0
+WM_CLOSE = 0x0010
+
user32 = ctypes.windll.user32
oleacc = ctypes.oledll.oleacc
oleaccMod = comtypes.client.GetModule("oleacc.dll")
IAccessible = oleaccMod.IAccessible
del oleaccMod
-OBJID_CLIENT = -4
-CHILDID_SELF = 0
-NAVRELATION_EMBEDS = 0x1009
# This is the path if running locally.
ia2Tlb = os.path.join(
os.getcwd(),
@@ -65,6 +74,13 @@ def AccessibleObjectFromWindow(hwnd, objectID=OBJID_CLIENT):
return p
+def getWindowClass(hwnd):
+ MAX_CHARS = 257
+ buffer = ctypes.create_unicode_buffer(MAX_CHARS)
+ user32.GetClassNameW(hwnd, buffer, MAX_CHARS)
+ return buffer.value
+
+
def getFirefoxHwnd():
"""Search all top level windows for the Firefox instance being
tested.
@@ -78,9 +94,7 @@ def getFirefoxHwnd():
@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
def callback(hwnd, lParam):
- name = ctypes.create_unicode_buffer(100)
- user32.GetClassNameW(hwnd, name, 100)
- if name.value != "MozillaWindowClass":
+ if getWindowClass(hwnd) != "MozillaWindowClass":
return True
pid = ctypes.wintypes.DWORD()
user32.GetWindowThreadProcessId(hwnd, byref(pid))
@@ -127,6 +141,106 @@ def findIa2ByDomId(root, id):
return descendant
+@dataclass
+class WinEvent:
+ event: int
+ hwnd: int
+ objectId: int
+ childId: int
+
+ def getIa2(self):
+ acc = ctypes.POINTER(IAccessible)()
+ child = comtypes.automation.VARIANT()
+ ctypes.oledll.oleacc.AccessibleObjectFromEvent(
+ self.hwnd,
+ self.objectId,
+ self.childId,
+ ctypes.byref(acc),
+ ctypes.byref(child),
+ )
+ if child.value != CHILDID_SELF:
+ # This isn't an IAccessible2 object.
+ return None
+ return toIa2(acc)
+
+
+class WaitForWinEvent:
+ """Wait for a win event, usually for IAccessible2.
+ This should be used as follows:
+ 1. Create an instance to wait for the desired event.
+ 2. Perform the action that should fire the event.
+ 3. Call wait() on the instance you created in 1) to wait for the event.
+ """
+
+ def __init__(self, eventId, match):
+ """event is the event id to wait for.
+ match is either None to match any object, an str containing the DOM id
+ of the desired object, or a function taking a WinEvent which should
+ return True if this is the requested event.
+ """
+ self._matched = None
+ # A kernel event used to signal when we get the desired event.
+ self._signal = ctypes.windll.kernel32.CreateEventW(None, True, False, None)
+
+ # We define this as a nested function because it has to be a static
+ # function, but we need a reference to self.
+ @ctypes.WINFUNCTYPE(
+ None,
+ ctypes.wintypes.HANDLE,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.HWND,
+ ctypes.wintypes.LONG,
+ ctypes.wintypes.LONG,
+ ctypes.wintypes.DWORD,
+ ctypes.wintypes.DWORD,
+ )
+ def winEventProc(hook, eventId, hwnd, objectId, childId, thread, time):
+ event = WinEvent(eventId, hwnd, objectId, childId)
+ if isinstance(match, str):
+ try:
+ ia2 = event.getIa2()
+ if f"id:{match};" in ia2.attributes:
+ self._matched = event
+ except (comtypes.COMError, TypeError):
+ pass
+ elif callable(match):
+ try:
+ if match(event):
+ self._matched = event
+ except Exception as e:
+ self._matched = e
+ if self._matched:
+ ctypes.windll.kernel32.SetEvent(self._signal)
+
+ self._hook = user32.SetWinEventHook(
+ eventId, eventId, None, winEventProc, 0, 0, WINEVENT_OUTOFCONTEXT
+ )
+ # Hold a reference to winEventProc so it doesn't get destroyed.
+ self._proc = winEventProc
+
+ def wait(self):
+ """Wait for and return the desired WinEvent."""
+ # Pump Windows messages until we get the desired event, which will be
+ # signalled using a kernel event.
+ handles = (ctypes.c_void_p * 1)(self._signal)
+ index = ctypes.wintypes.DWORD()
+ TIMEOUT = 10000
+ try:
+ ctypes.oledll.ole32.CoWaitForMultipleHandles(
+ COWAIT_DEFAULT, TIMEOUT, 1, handles, ctypes.byref(index)
+ )
+ except WindowsError as e:
+ if e.winerror == RPC_S_CALLPENDING:
+ raise TimeoutError("Timeout before desired event received")
+ raise
+ finally:
+ user32.UnhookWinEvent(self._hook)
+ self._proc = None
+ if isinstance(self._matched, Exception):
+ raise self._matched from self._matched
+ return self._matched
+
+
def getDocUia():
"""Get the IUIAutomationElement for the document being tested."""
# We start with IAccessible2 because there's no efficient way to
diff --git a/accessible/tests/browser/windows/ia2/browser.toml b/accessible/tests/browser/windows/ia2/browser.toml
index d72b5f8a2d..d6226b73cc 100644
--- a/accessible/tests/browser/windows/ia2/browser.toml
+++ b/accessible/tests/browser/windows/ia2/browser.toml
@@ -6,4 +6,6 @@ skip-if = [
]
support-files = ["head.js"]
+["browser_osPicker.js"]
+
["browser_role.js"]
diff --git a/accessible/tests/browser/windows/ia2/browser_osPicker.js b/accessible/tests/browser/windows/ia2/browser_osPicker.js
new file mode 100644
index 0000000000..b14f2d0a5f
--- /dev/null
+++ b/accessible/tests/browser/windows/ia2/browser_osPicker.js
@@ -0,0 +1,51 @@
+/* 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";
+
+addAccessibleTask(
+ `<input id="file" type="file">`,
+ async function (browser, docAcc) {
+ info("Focusing file input");
+ await runPython(`
+ global focused
+ focused = WaitForWinEvent(EVENT_OBJECT_FOCUS, "file")
+ `);
+ const file = findAccessibleChildByID(docAcc, "file");
+ file.takeFocus();
+ await runPython(`
+ focused.wait()
+ `);
+ ok(true, "file input got focus");
+ info("Opening file picker");
+ await runPython(`
+ global focused
+ focused = WaitForWinEvent(
+ EVENT_OBJECT_FOCUS,
+ lambda evt: getWindowClass(evt.hwnd) == "Edit"
+ )
+ `);
+ file.doAction(0);
+ await runPython(`
+ global event
+ event = focused.wait()
+ `);
+ ok(true, "Picker got focus");
+ info("Dismissing picker");
+ await runPython(`
+ # If the picker is dismissed too quickly, it seems to re-enable the root
+ # window before we do. This sleep isn't ideal, but it's more likely to
+ # reproduce the case that our root window gets focus before it is enabled.
+ # See bug 1883568 for further details.
+ import time
+ time.sleep(1)
+ focused = WaitForWinEvent(EVENT_OBJECT_FOCUS, "file")
+ # Sending key presses to the picker is unreliable, so use WM_CLOSE.
+ pickerRoot = user32.GetAncestor(event.hwnd, GA_ROOT)
+ user32.SendMessageW(pickerRoot, WM_CLOSE, 0, 0)
+ focused.wait()
+ `);
+ ok(true, "file input got focus");
+ }
+);
diff --git a/accessible/tests/browser/windows/ia2/browser_role.js b/accessible/tests/browser/windows/ia2/browser_role.js
index 08e44c280f..89b560ab49 100644
--- a/accessible/tests/browser/windows/ia2/browser_role.js
+++ b/accessible/tests/browser/windows/ia2/browser_role.js
@@ -12,7 +12,7 @@ addAccessibleTask(
`
<p id="p">p</p>
`,
- async function (browser, docAcc) {
+ async function () {
let role = await runPython(`
global doc
doc = getDocIa2()
diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml
index f7974d69c7..d1513c1822 100644
--- a/accessible/tests/browser/windows/uia/browser.toml
+++ b/accessible/tests/browser/windows/uia/browser.toml
@@ -9,3 +9,5 @@ support-files = ["head.js"]
["browser_controlType.js"]
["browser_elementFromPoint.js"]
+
+["browser_tree.js"]
diff --git a/accessible/tests/browser/windows/uia/browser_controlType.js b/accessible/tests/browser/windows/uia/browser_controlType.js
index 16db892581..3bb994f437 100644
--- a/accessible/tests/browser/windows/uia/browser_controlType.js
+++ b/accessible/tests/browser/windows/uia/browser_controlType.js
@@ -9,11 +9,11 @@ const UIA_ButtonControlTypeId = 50000;
const UIA_DocumentControlTypeId = 50030;
/* eslint-enable camelcase */
-addAccessibleTask(
+addUiaTask(
`
<button id="button">button</button>
`,
- async function (browser, docAcc) {
+ async function () {
let controlType = await runPython(`
global doc
doc = getDocUia()
diff --git a/accessible/tests/browser/windows/uia/browser_elementFromPoint.js b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js
index e2fda4ab30..acf6fe91c7 100644
--- a/accessible/tests/browser/windows/uia/browser_elementFromPoint.js
+++ b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js
@@ -4,12 +4,12 @@
"use strict";
-addAccessibleTask(
+addUiaTask(
`
<button id="button">button</p>
<a id="a" href="#">a</a>
`,
- async function (browser, docAcc) {
+ async function () {
ok(
await runPython(`
global doc
diff --git a/accessible/tests/browser/windows/uia/browser_tree.js b/accessible/tests/browser/windows/uia/browser_tree.js
new file mode 100644
index 0000000000..778609bedb
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_tree.js
@@ -0,0 +1,104 @@
+/* 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";
+
+async function testIsControl(pyVar, isControl) {
+ const result = await runPython(`bool(${pyVar}.CurrentIsControlElement)`);
+ if (isControl) {
+ ok(result, `${pyVar} is a control element`);
+ } else {
+ ok(!result, `${pyVar} isn't a control element`);
+ }
+}
+
+/**
+ * Define a global Python variable and assign it to a given Python expression.
+ */
+function definePyVar(varName, expression) {
+ return runPython(`
+ global ${varName}
+ ${varName} = ${expression}
+ `);
+}
+
+/**
+ * Get the UIA element with the given id and assign it to a global Python
+ * variable using the id as the variable name.
+ */
+function assignPyVarToUiaWithId(id) {
+ return definePyVar(id, `findUiaByDomId(doc, "${id}")`);
+}
+
+addUiaTask(
+ `
+<p id="p">paragraph</p>
+<div id="div">div</div>
+<!-- The spans are because the UIA -> IA2 proxy seems to remove a single text
+ leaf child from even the raw tree.
+ -->
+<a id="link" href="#">link<span> </span>></a>
+<h1 id="h1">h1<span> </span></h1>
+<h1 id="h1WithDiv"><div>h1 with div<span> </span></div></h1>
+<input id="range" type="range">
+<div onclick=";" id="clickable">clickable</div>
+<div id="editable" contenteditable>editable</div>
+<table id="table"><tr><th>th</th></tr></table>
+ `,
+ async function (browser, docAcc) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("p");
+ await testIsControl("p", false);
+ await definePyVar(
+ "pTextLeaf",
+ `uiaClient.RawViewWalker.GetFirstChildElement(p)`
+ );
+ await testIsControl("pTextLeaf", true);
+ await assignPyVarToUiaWithId("div");
+ await testIsControl("div", false);
+ await definePyVar(
+ "divTextLeaf",
+ `uiaClient.RawViewWalker.GetFirstChildElement(div)`
+ );
+ await testIsControl("divTextLeaf", true);
+ await assignPyVarToUiaWithId("link");
+ await testIsControl("link", true);
+ await assignPyVarToUiaWithId("range");
+ await testIsControl("range", true);
+ await assignPyVarToUiaWithId("editable");
+ await testIsControl("editable", true);
+ await assignPyVarToUiaWithId("table");
+ await testIsControl("table", true);
+ if (!gIsUiaEnabled) {
+ // The remaining tests are broken with the UIA -> IA2 proxy.
+ return;
+ }
+ await definePyVar(
+ "linkTextLeaf",
+ `uiaClient.RawViewWalker.GetFirstChildElement(link)`
+ );
+ await testIsControl("linkTextLeaf", false);
+ await assignPyVarToUiaWithId("h1");
+ await testIsControl("h1", true);
+ await definePyVar(
+ "h1TextLeaf",
+ `uiaClient.RawViewWalker.GetFirstChildElement(h1)`
+ );
+ await testIsControl("h1TextLeaf", false);
+ await assignPyVarToUiaWithId("h1WithDiv");
+ await testIsControl("h1WithDiv", true);
+ // h1WithDiv's text leaf is its grandchild.
+ await definePyVar(
+ "h1WithDivTextLeaf",
+ `uiaClient.RawViewWalker.GetFirstChildElement(
+ uiaClient.RawViewWalker.GetFirstChildElement(
+ h1WithDiv
+ )
+ )`
+ );
+ await testIsControl("h1WithDivTextLeaf", false);
+ await assignPyVarToUiaWithId("clickable");
+ await testIsControl("clickable", true);
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js
index afc50984bd..e659354c7c 100644
--- a/accessible/tests/browser/windows/uia/head.js
+++ b/accessible/tests/browser/windows/uia/head.js
@@ -4,6 +4,8 @@
"use strict";
+/* exported gIsUiaEnabled, addUiaTask */
+
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
@@ -16,3 +18,38 @@ loadScripts(
{ name: "common.js", dir: MOCHITESTS_DIR },
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
);
+
+let gIsUiaEnabled = false;
+
+/**
+ * This is like addAccessibleTask, but takes two additional boolean options:
+ * - uiaEnabled: Whether to run a variation of this test with Gecko UIA enabled.
+ * - uiaDisabled: Whether to run a variation of this test with UIA disabled. In
+ * this case, UIA will rely entirely on the IA2 -> UIA proxy.
+ * If both are set, the test will be run twice with different configurations.
+ * You can determine which variant is currently running using the gIsUiaEnabled
+ * variable. This is useful for conditional tests; e.g. if Gecko UIA supports
+ * something that the IA2 -> UIA proxy doesn't support.
+ */
+function addUiaTask(doc, task, options = {}) {
+ const { uiaEnabled = true, uiaDisabled = true } = options;
+
+ function addTask(shouldEnable) {
+ async function uiaTask(browser, docAcc, topDocAcc) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["accessibility.uia.enable", shouldEnable]],
+ });
+ gIsUiaEnabled = shouldEnable;
+ info(shouldEnable ? "Gecko UIA enabled" : "Gecko UIA disabled");
+ await task(browser, docAcc, topDocAcc);
+ }
+ addAccessibleTask(doc, uiaTask, options);
+ }
+
+ if (uiaEnabled) {
+ addTask(true);
+ }
+ if (uiaDisabled) {
+ addTask(false);
+ }
+}
diff --git a/accessible/tests/crashtests/crashtests.list b/accessible/tests/crashtests/crashtests.list
index 2f628ddfe2..4787e09163 100644
--- a/accessible/tests/crashtests/crashtests.list
+++ b/accessible/tests/crashtests/crashtests.list
@@ -1,5 +1,5 @@
load 448064.xhtml # This test instantiates a11y, so be careful about adding tests before it
-asserts-if(!browserIsRemote,2) load 884202.html
+load 884202.html
load 1572811.html
load 1578282.html
load 890760.html
diff --git a/accessible/tests/mochitest/actions/test_media.html b/accessible/tests/mochitest/actions/test_media.html
index 5327901e56..99393b984b 100644
--- a/accessible/tests/mochitest/actions/test_media.html
+++ b/accessible/tests/mochitest/actions/test_media.html
@@ -32,7 +32,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
this.getID = function focusChecker_getID() {
return "focus handling";
};
- this.check = function focusChecker_check(aEvent) {
+ this.check = function focusChecker_check() {
testStates(this.target, STATE_FOCUSED);
};
}
diff --git a/accessible/tests/mochitest/actions/test_tree.xhtml b/accessible/tests/mochitest/actions/test_tree.xhtml
index 17710cbdce..3279ac74ee 100644
--- a/accessible/tests/mochitest/actions/test_tree.xhtml
+++ b/accessible/tests/mochitest/actions/test_tree.xhtml
@@ -31,7 +31,7 @@
{
this.__proto__ = new focusChecker(aAcc);
- this.check = function focusChecker_check(aEvent)
+ this.check = function focusChecker_check()
{
var states = aStates ? aStates : 0;
testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
@@ -71,7 +71,7 @@
actionIndex: 1,
events: CLICK_EVENTS,
targetID: treeBodyNode,
- checkOnClickEvent: function check(aEvent)
+ checkOnClickEvent: function check()
{
testStates(this.ID, STATE_EXPANDED);
}
@@ -82,7 +82,7 @@
actionIndex: 1,
events: CLICK_EVENTS,
targetID: treeBodyNode,
- checkOnClickEvent: function check(aEvent)
+ checkOnClickEvent: function check()
{
testStates(this.ID, STATE_COLLAPSED);
}
diff --git a/accessible/tests/mochitest/actions/test_treegrid.xhtml b/accessible/tests/mochitest/actions/test_treegrid.xhtml
index 1c6e1bb8aa..985018418b 100644
--- a/accessible/tests/mochitest/actions/test_treegrid.xhtml
+++ b/accessible/tests/mochitest/actions/test_treegrid.xhtml
@@ -37,7 +37,7 @@
{
return "focus handling";
}
- this.check = function focusChecker_check(aEvent)
+ this.check = function focusChecker_check()
{
var states = aStates ? aStates : 0;
testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
@@ -52,7 +52,7 @@
{
return "state change handling";
}
- this.check = function stateChangeChecker_check(aEvent)
+ this.check = function stateChangeChecker_check()
{
if (aIsEnabled)
testStates(this.target, STATE_CHECKED);
@@ -95,7 +95,7 @@
actionIndex: 1,
events: CLICK_EVENTS,
targetID: treeBodyNode,
- check: function check(aEvent)
+ check: function check()
{
testStates(this.ID, STATE_EXPANDED);
}
@@ -106,7 +106,7 @@
actionIndex: 1,
events: CLICK_EVENTS,
targetID: treeBodyNode,
- check: function check(aEvent)
+ check: function check()
{
testStates(this.ID, STATE_COLLAPSED);
}
diff --git a/accessible/tests/mochitest/autocomplete.js b/accessible/tests/mochitest/autocomplete.js
index bd5458c51d..baf9529473 100644
--- a/accessible/tests/mochitest/autocomplete.js
+++ b/accessible/tests/mochitest/autocomplete.js
@@ -171,11 +171,11 @@ AutoCompleteResult.prototype = {
return this.comments[aIndex];
},
- getStyleAt(aIndex) {
+ getStyleAt() {
return null;
},
- getImageAt(aIndex) {
+ getImageAt() {
return "";
},
@@ -183,11 +183,11 @@ AutoCompleteResult.prototype = {
return this.getValueAt(aIndex);
},
- isRemovableAt(aRowIndex) {
+ isRemovableAt() {
return true;
},
- removeValueAt(aRowIndex) {},
+ removeValueAt() {},
// nsISupports implementation
QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]),
diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js
index f0ee117452..7f40c33bff 100644
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -976,7 +976,7 @@ function prettyName(aIdentifier) {
* @param aString the string to shorten.
* @returns the shortened string.
*/
-function shortenString(aString, aMaxLength) {
+function shortenString(aString) {
if (aString.length <= MAX_TRIM_LENGTH) {
return aString;
}
diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js
index a6c216e01d..d4e3221f95 100644
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -928,10 +928,7 @@ function eventQueue(aEventType) {
return invoker.getID();
};
- this.setInvokerStatus = function eventQueue_setInvokerStatus(
- aStatus,
- aLogMsg
- ) {
+ this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus) {
this.mNextInvokerStatus = aStatus;
// Uncomment it to debug invoker processing logic.
@@ -2348,7 +2345,7 @@ var gA11yEventApplicantsCount = 0;
var gA11yEventObserver = {
// eslint-disable-next-line complexity
- observe: function observe(aSubject, aTopic, aData) {
+ observe: function observe(aSubject, aTopic) {
if (aTopic != "accessible-event") {
return;
}
diff --git a/accessible/tests/mochitest/events/a11y.toml b/accessible/tests/mochitest/events/a11y.toml
index 501b59f7b7..81f9fab3d9 100644
--- a/accessible/tests/mochitest/events/a11y.toml
+++ b/accessible/tests/mochitest/events/a11y.toml
@@ -40,8 +40,6 @@ support-files = [
["test_flush.html"]
-["test_focus_aria_activedescendant.html"]
-
["test_focus_autocomplete.html"]
["test_focus_autocomplete.xhtml"]
diff --git a/accessible/tests/mochitest/events/test_contextmenu.html b/accessible/tests/mochitest/events/test_contextmenu.html
index 6200efc00d..3047b4d46c 100644
--- a/accessible/tests/mochitest/events/test_contextmenu.html
+++ b/accessible/tests/mochitest/events/test_contextmenu.html
@@ -52,7 +52,7 @@
};
}
- function closeContextMenu(aID) {
+ function closeContextMenu() {
this.eventSeq = [
new invokerChecker(EVENT_MENUPOPUP_END,
getAccessible(getContextMenuNode())),
diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
deleted file mode 100644
index 661284619a..0000000000
--- a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
+++ /dev/null
@@ -1,327 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=429547
--->
-<head>
- <title>aria-activedescendant focus tests</title>
- <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-
- <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-
- <script type="application/javascript"
- src="../common.js"></script>
- <script type="application/javascript"
- src="../role.js"></script>
- <script type="application/javascript"
- src="../states.js"></script>
- <script type="application/javascript"
- src="../events.js"></script>
-
- <script type="application/javascript">
- let PromEvents = {};
- Services.scriptloader.loadSubScript(
- "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js",
- PromEvents);
- // gA11yEventDumpToConsole = true; // debugging
-
- function changeARIAActiveDescendant(aContainer, aItem, aPrevItemId) {
- let itemID = Node.isInstance(aItem) ? aItem.id : aItem;
- this.eventSeq = [new focusChecker(aItem)];
-
- if (aPrevItemId) {
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
- );
- }
-
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aItem)
- );
-
- this.invoke = function changeARIAActiveDescendant_invoke() {
- getNode(aContainer).setAttribute("aria-activedescendant", itemID);
- };
-
- this.getID = function changeARIAActiveDescendant_getID() {
- return "change aria-activedescendant on " + itemID;
- };
- }
-
- function clearARIAActiveDescendant(aID, aPrevItemId) {
- this.eventSeq = [
- new focusChecker(aID),
- ];
-
- if (aPrevItemId) {
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
- );
- }
-
- this.invoke = function clearARIAActiveDescendant_invoke() {
- getNode(aID).removeAttribute("aria-activedescendant");
- };
-
- this.getID = function clearARIAActiveDescendant_getID() {
- return "clear aria-activedescendant on container " + aID;
- };
- }
-
- /**
- * Change aria-activedescendant to an invalid (non-existent) id.
- * Ensure that focus is fired on the element itself.
- */
- function changeARIAActiveDescendantInvalid(aID, aInvalidID, aPrevItemId) {
- if (!aInvalidID) {
- aInvalidID = "invalid";
- }
-
- this.eventSeq = [
- new focusChecker(aID),
- ];
-
- if (aPrevItemId) {
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
- );
- }
-
- this.invoke = function changeARIAActiveDescendant_invoke() {
- getNode(aID).setAttribute("aria-activedescendant", aInvalidID);
- };
-
- this.getID = function changeARIAActiveDescendant_getID() {
- return "change aria-activedescendant to invalid id";
- };
- }
-
- function insertItemNFocus(aID, aNewItemID, aPrevItemId) {
- this.eventSeq = [
- new invokerChecker(EVENT_SHOW, aNewItemID),
- new focusChecker(aNewItemID),
- ];
-
- if (aPrevItemId) {
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId)
- );
- }
-
- this.eventSeq.push(
- new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aNewItemID)
- );
-
- this.invoke = function insertItemNFocus_invoke() {
- var container = getNode(aID);
-
- var itemNode = document.createElement("div");
- itemNode.setAttribute("id", aNewItemID);
- itemNode.setAttribute("role", "listitem");
- itemNode.textContent = aNewItemID;
- container.appendChild(itemNode);
-
- container.setAttribute("aria-activedescendant", aNewItemID);
- };
-
- this.getID = function insertItemNFocus_getID() {
- return "insert new node and focus it with ID: " + aNewItemID;
- };
- }
-
- /**
- * Change the id of an element to another id which is the target of
- * aria-activedescendant.
- * If another element already has the desired id, remove it from that
- * element first.
- * Ensure that focus is fired on the target element which was given the
- * desired id.
- * @param aFromID The existing id of the target element.
- * @param aToID The desired id to be given to the target element.
- */
- function moveARIAActiveDescendantID(aFromID, aToID) {
- this.eventSeq = [
- new focusChecker(aToID),
- new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aToID),
- ];
-
- this.invoke = function moveARIAActiveDescendantID_invoke() {
- let orig = document.getElementById(aToID);
- if (orig) {
- orig.id = "";
- }
- document.getElementById(aFromID).id = aToID;
- };
-
- this.getID = function moveARIAActiveDescendantID_getID() {
- return "move aria-activedescendant id " + aToID;
- };
- }
-
- var gQueue = null;
- async function doTest() {
- gQueue = new eventQueue();
- // Later tests use await.
- let queueFinished = new Promise(resolve => {
- gQueue.onFinish = function() {
- resolve();
- return DO_NOT_FINISH_TEST;
- };
- });
-
- gQueue.push(new synthFocus("listbox", new focusChecker("item1")));
- gQueue.push(new changeARIAActiveDescendant("listbox", "item2", "item1"));
- gQueue.push(new changeARIAActiveDescendant("listbox", "item3", "item2"));
-
- gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry")));
- gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2"));
-
- gQueue.push(new synthFocus("listbox", new focusChecker("item3")));
- gQueue.push(new insertItemNFocus("listbox", "item4", "item3"));
-
- gQueue.push(new clearARIAActiveDescendant("listbox", "item4"));
- gQueue.push(new changeARIAActiveDescendant("listbox", "item1"));
- gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "invalid", "item1"));
-
- gQueue.push(new changeARIAActiveDescendant("listbox", "roaming"));
- gQueue.push(new moveARIAActiveDescendantID("roaming2", "roaming"));
- gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "roaming3", "roaming"));
- gQueue.push(new moveARIAActiveDescendantID("roaming", "roaming3"));
-
- gQueue.push(new synthFocus("activedesc_nondesc_input",
- new focusChecker("activedesc_nondesc_option")));
-
- let shadowRoot = document.getElementById("shadow").shadowRoot;
- let shadowListbox = shadowRoot.getElementById("shadowListbox");
- let shadowItem1 = shadowRoot.getElementById("shadowItem1");
- let shadowItem2 = shadowRoot.getElementById("shadowItem2");
- gQueue.push(new synthFocus(shadowListbox, new focusChecker(shadowItem1)));
- gQueue.push(new changeARIAActiveDescendant(shadowListbox, shadowItem2));
-
- gQueue.invoke();
- await queueFinished;
- // Tests beyond this point use await rather than eventQueue.
-
- info("Testing simultaneous insertion, relocation and aria-activedescendant");
- let comboboxWithHiddenList = getNode("comboboxWithHiddenList");
- let evtProm = PromEvents.waitForEvent(EVENT_FOCUS, comboboxWithHiddenList);
- comboboxWithHiddenList.focus();
- await evtProm;
- testStates(comboboxWithHiddenList, STATE_FOCUSED);
- // hiddenList is owned, so unhiding causes insertion and relocation.
- getNode("hiddenList").hidden = false;
- evtProm = Promise.all([
- PromEvents.waitForEvent(EVENT_FOCUS, "hiddenListOption"),
- PromEvents.waitForStateChange("hiddenListOption", EXT_STATE_ACTIVE, true, true),
- ]);
- comboboxWithHiddenList.setAttribute("aria-activedescendant", "hiddenListOption");
- await evtProm;
- testStates("hiddenListOption", STATE_FOCUSED);
-
- info("Testing active state changes when not focused");
- testStates("listbox", 0, 0, STATE_FOCUSED);
- evtProm = Promise.all([
- PromEvents.waitForStateChange("roaming3", EXT_STATE_ACTIVE, false, true),
- PromEvents.waitForStateChange("item1", EXT_STATE_ACTIVE, true, true),
- ]);
- getNode("listbox").setAttribute("aria-activedescendant", "item1");
- await evtProm;
-
- info("Testing that focus is always fired first");
- const listbox = getNode("listbox");
- evtProm = PromEvents.waitForEvent(EVENT_FOCUS, "item1");
- listbox.focus();
- await evtProm;
- const item1 = getNode("item1");
- evtProm = PromEvents.waitForOrderedEvents([
- [EVENT_FOCUS, "item2"],
- [EVENT_NAME_CHANGE, item1],
- ], "Focus then name change");
- item1.setAttribute("aria-label", "changed");
- listbox.setAttribute("aria-activedescendant", "item2");
- await evtProm;
-
- info("Setting aria-activedescendant to invalid id on non-focused node");
- const combobox_entry = getNode("combobox_entry");
- evtProm = PromEvents.waitForEvents({
- expected: [[EVENT_FOCUS, combobox_entry]],
- unexpected: [[EVENT_FOCUS, listbox]],
- });
- combobox_entry.focus();
- listbox.setAttribute("aria-activedescendant", "invalid");
- await evtProm;
-
- SimpleTest.finish();
- }
-
- SimpleTest.waitForExplicitFinish();
- addA11yLoadEvent(doTest);
- </script>
-</head>
-<body>
-
- <a target="_blank"
- href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
- title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
- Mozilla Bug 429547
- </a>
- <a target="_blank"
- href="https://bugzilla.mozilla.org/show_bug.cgi?id=761102"
- title="Focus may be missed when ARIA active-descendant is changed on active composite widget">
- Mozilla Bug 761102
- </a>
- <p id="display"></p>
- <div id="content" style="display: none"></div>
- <pre id="test">
- </pre>
-
- <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1"
- aria-owns="item3">
- <div role="listitem" id="item1">item1</div>
- <div role="listitem" id="item2">item2</div>
- <div role="listitem" id="roaming">roaming</div>
- <div role="listitem" id="roaming2">roaming2</div>
- </div>
- <div role="listitem" id="item3">item3</div>
-
- <div role="combobox" id="combobox">
- <input id="combobox_entry">
- <ul>
- <li role="option" id="combobox_option1">option1</li>
- <li role="option" id="combobox_option2">option2</li>
- </ul>
- </div>
-
- <!-- aria-activedescendant targeting a non-descendant -->
- <input id="activedesc_nondesc_input" aria-activedescendant="activedesc_nondesc_option">
- <div role="listbox">
- <div role="option" id="activedesc_nondesc_option">option</div>
- </div>
-
- <div id="shadow"></div>
- <script>
- let host = document.getElementById("shadow");
- let shadow = host.attachShadow({mode: "open"});
- let listbox = document.createElement("div");
- listbox.id = "shadowListbox";
- listbox.setAttribute("role", "listbox");
- listbox.setAttribute("tabindex", "0");
- shadow.appendChild(listbox);
- let item = document.createElement("div");
- item.id = "shadowItem1";
- item.setAttribute("role", "option");
- listbox.appendChild(item);
- listbox.setAttribute("aria-activedescendant", "shadowItem1");
- item = document.createElement("div");
- item.id = "shadowItem2";
- item.setAttribute("role", "option");
- listbox.appendChild(item);
- </script>
-
- <div id="comboboxWithHiddenList" tabindex="0" role="combobox" aria-owns="hiddenList">
- </div>
- <div id="hiddenList" hidden role="listbox">
- <div id="hiddenListOption" role="option"></div>
- </div>
-</body>
-</html>
diff --git a/accessible/tests/mochitest/events/test_focus_doc.html b/accessible/tests/mochitest/events/test_focus_doc.html
index a35fc06ed0..bfb43895bd 100644
--- a/accessible/tests/mochitest/events/test_focus_doc.html
+++ b/accessible/tests/mochitest/events/test_focus_doc.html
@@ -47,7 +47,17 @@
// focus on not editable document
gQueue.push(new synthFocus(frame2InputAcc));
- gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc)));
+ var canTabMoveFocusToRootElement =
+ !SpecialPowers.getBoolPref("dom.disable_tab_focus_to_root_element");
+ if (canTabMoveFocusToRootElement) {
+ // Moves the focus to the root element
+ gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc)));
+ } else {
+ // Skips the root element, so the focus got moved to buttonAcc2.
+ var buttonAcc2 = getAccessible("b2");
+ gQueue.push(new synthShiftTab(buttonAcc2, new focusChecker(buttonAcc2)));
+ }
+
gQueue.invoke(); // Will call SimpleTest.finish();
}
diff --git a/accessible/tests/mochitest/events/test_label.xhtml b/accessible/tests/mochitest/events/test_label.xhtml
index 5780629dc6..2f207eba65 100644
--- a/accessible/tests/mochitest/events/test_label.xhtml
+++ b/accessible/tests/mochitest/events/test_label.xhtml
@@ -93,7 +93,7 @@
/**
* Change @crop attribute.
*/
- function setCrop(aID, aCropValue, aRemovedText, aInsertedText)
+ function setCrop(aID, aCropValue)
{
this.labelNode = getNode(aID);
this.width = this.labelNode.getBoundingClientRect().width;
diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html
index 0642851408..0ecf54b0d1 100644
--- a/accessible/tests/mochitest/events/test_statechange.html
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -28,7 +28,7 @@
await p;
}
- async function makeEditableDoc(aDocNode, aIsEnabled) {
+ async function makeEditableDoc(aDocNode) {
let p = waitForStateChange(aDocNode, EXT_STATE_EDITABLE, true, true);
aDocNode.designMode = "on";
await p;
diff --git a/accessible/tests/mochitest/name/markup.js b/accessible/tests/mochitest/name/markup.js
index a267bd4f7b..4ad478482b 100644
--- a/accessible/tests/mochitest/name/markup.js
+++ b/accessible/tests/mochitest/name/markup.js
@@ -335,7 +335,7 @@ function testNameForElmRule(aElm, aRule) {
parentNode.removeChild(labelElm);
}
-function testNameForSubtreeRule(aElm, aRule) {
+function testNameForSubtreeRule(aElm) {
var msg = "From subtree test (" + gTestIterator.testID + ").";
testName(aElm, aElm.getAttribute("textequiv"), msg);
testAbsentAttrs(aElm, { "explicit-name": "true" });
diff --git a/accessible/tests/mochitest/name/test_tree.xhtml b/accessible/tests/mochitest/name/test_tree.xhtml
index 3564481d00..282e4ea118 100644
--- a/accessible/tests/mochitest/name/test_tree.xhtml
+++ b/accessible/tests/mochitest/name/test_tree.xhtml
@@ -31,7 +31,7 @@
this.DOMNode.view = new nsTreeTreeView();
}
- this.check = function treeTester_check(aEvent)
+ this.check = function treeTester_check()
{
var tree = {
role: ROLE_OUTLINE,
@@ -89,7 +89,7 @@
this.DOMNode.view = new nsTableTreeView(2);
}
- this.check = function tableTester_check(aEvent)
+ this.check = function tableTester_check()
{
var tree = {
role: aIsTable ? ROLE_TABLE : ROLE_TREE_TABLE,
diff --git a/accessible/tests/mochitest/promisified-events.js b/accessible/tests/mochitest/promisified-events.js
index c58466dcf1..223be16147 100644
--- a/accessible/tests/mochitest/promisified-events.js
+++ b/accessible/tests/mochitest/promisified-events.js
@@ -141,7 +141,7 @@ function matchEvent(event, matchCriteria) {
function waitForEvent(eventType, matchCriteria, message) {
return new Promise(resolve => {
let eventObserver = {
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic !== "accessible-event") {
return;
}
@@ -185,7 +185,7 @@ class UnexpectedEvents {
}
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic !== "accessible-event") {
return;
}
diff --git a/accessible/tests/mochitest/relations/test_tabbrowser.xhtml b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml
index 3356bc6140..4820419c1e 100644
--- a/accessible/tests/mochitest/relations/test_tabbrowser.xhtml
+++ b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml
@@ -43,7 +43,7 @@
browserDocument().documentElement.getBoundingClientRect();
}
- this.finalCheck = function testTabRelations_finalCheck(aEvent)
+ this.finalCheck = function testTabRelations_finalCheck()
{
////////////////////////////////////////////////////////////////////////
// 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
diff --git a/accessible/tests/mochitest/role.js b/accessible/tests/mochitest/role.js
index 07b820c2ac..66cb6de4d2 100644
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -40,6 +40,7 @@ const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION;
const ROLE_FORM = nsIAccessibleRole.ROLE_FORM;
const ROLE_FORM_LANDMARK = nsIAccessibleRole.ROLE_FORM_LANDMARK;
const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC;
+const ROLE_GRID = nsIAccessibleRole.ROLE_GRID;
const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL;
const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING;
const ROLE_HEADING = nsIAccessibleRole.ROLE_HEADING;
diff --git a/accessible/tests/mochitest/role/test_aria.html b/accessible/tests/mochitest/role/test_aria.html
index bd7ffd27fb..0e7873c485 100644
--- a/accessible/tests/mochitest/role/test_aria.html
+++ b/accessible/tests/mochitest/role/test_aria.html
@@ -56,16 +56,16 @@
testRole("aria_directory_mixed", ROLE_LIST);
testRole("aria_document", ROLE_NON_NATIVE_DOCUMENT);
testRole("aria_document_mixed", ROLE_NON_NATIVE_DOCUMENT);
- testRole("aria_form", ROLE_FORM);
- testRole("aria_form_mixed", ROLE_FORM);
+ testRole("aria_form", ROLE_TEXT);
+ testRole("aria_form_mixed", ROLE_TEXT);
testRole("aria_form_with_label", ROLE_FORM);
testRole("aria_form_with_label_mixed", ROLE_FORM);
testRole("aria_feed", ROLE_GROUPING);
testRole("aria_feed_mixed", ROLE_GROUPING);
testRole("aria_figure", ROLE_FIGURE);
testRole("aria_figure_mixed", ROLE_FIGURE);
- testRole("aria_grid", ROLE_TABLE);
- testRole("aria_grid_mixed", ROLE_TABLE);
+ testRole("aria_grid", ROLE_GRID);
+ testRole("aria_grid_mixed", ROLE_GRID);
testRole("aria_gridcell", ROLE_GRID_CELL);
testRole("aria_gridcell_mixed", ROLE_GRID_CELL);
testRole("aria_group", ROLE_GROUPING);
@@ -184,8 +184,10 @@
testRole("articlemain", ROLE_LANDMARK);
testRole("articlemain_mixed", ROLE_LANDMARK);
- testRole("articleform", ROLE_FORM);
- testRole("articleform_mixed", ROLE_FORM);
+ testRole("articleform", ROLE_ARTICLE);
+ testRole("articleform_mixed", ROLE_ARTICLE);
+ testRole("articleform_label", ROLE_FORM);
+ testRole("articleform_label_mixed", ROLE_FORM);
// Test article exposed as article
testRole("testArticle", ROLE_ARTICLE);
@@ -204,8 +206,10 @@
// strong landmark
testRole("application", ROLE_APPLICATION);
testRole("application_mixed", ROLE_APPLICATION);
- testRole("form", ROLE_FORM);
- testRole("form_mixed", ROLE_FORM);
+ testRole("form", ROLE_SECTION);
+ testRole("form_mixed", ROLE_SECTION);
+ testRole("form_label", ROLE_FORM);
+ testRole("form_label_mixed", ROLE_FORM);
testRole("application_table", ROLE_APPLICATION);
testRole("application_table_mixed", ROLE_APPLICATION);
@@ -535,6 +539,8 @@
<article id="articlemain_mixed" role="mAIn">a main area</article>
<article id="articleform" role="form">a form area</article>
<article id="articleform_mixed" role="fORm">a form area</article>
+ <article id="articleform_label" aria-label="form" role="form">a form area</article>
+ <article id="articleform_label_mixed" aria-label="form" role="fORm">a form area</article>
<div id="testArticle" role="article" title="Test article">
<p>This is a paragraph inside the article.</p>
@@ -561,6 +567,8 @@
<div role="aPPLICATIOn" id="application_mixed">application</div>
<div role="form" id="form">form</div>
<div role="fORm" id="form_mixed">form</div>
+ <div role="form" id="form_label" aria-label="form">form</div>
+ <div role="fORm" id="form_label_mixed" aria-label="form">form</div>
<!-- weak landmarks -->
<div role="banner" id="banner">banner</div>
diff --git a/accessible/tests/mochitest/role/test_general.html b/accessible/tests/mochitest/role/test_general.html
index 38dde3325a..c0afd39360 100644
--- a/accessible/tests/mochitest/role/test_general.html
+++ b/accessible/tests/mochitest/role/test_general.html
@@ -161,7 +161,7 @@
id="nav_overflow">overflow nav</nav>
<header style="overflow: hidden;"
id="header_overflow">overflow header</header>
- <aside style="overflow: hidden;"
+ <aside style="overflow: hidden;" aria-label="aside"
id="aside_overflow">overflow aside</aside>
<footer style="overflow: hidden;"
id="footer_overflow">overflow footer</footer>
diff --git a/accessible/tests/mochitest/states/test_aria_widgetitems.html b/accessible/tests/mochitest/states/test_aria_widgetitems.html
index c2d546ba01..d17f110c22 100644
--- a/accessible/tests/mochitest/states/test_aria_widgetitems.html
+++ b/accessible/tests/mochitest/states/test_aria_widgetitems.html
@@ -26,7 +26,7 @@
this.DOMNode.focus();
};
- this.check = function focusARIAItem_check(aEvent) {
+ this.check = function focusARIAItem_check() {
testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
aIsSelected ? 0 : STATE_SELECTED);
};
@@ -47,7 +47,7 @@
this.widgetDOMNode.focus();
};
- this.check = function focusActiveDescendantItem_check(aEvent) {
+ this.check = function focusActiveDescendantItem_check() {
testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
aIsSelected ? 0 : STATE_SELECTED);
};
diff --git a/accessible/tests/mochitest/test_bug420863.html b/accessible/tests/mochitest/test_bug420863.html
index 4f3a608fe1..9d515ab3c6 100644
--- a/accessible/tests/mochitest/test_bug420863.html
+++ b/accessible/tests/mochitest/test_bug420863.html
@@ -40,7 +40,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=420863
// register 'click' event handler
gClickHandler = {
- handleEvent: function handleEvent(aEvent) {
+ handleEvent: function handleEvent() {
},
};
td3Node.addEventListener("click", gClickHandler);
@@ -58,7 +58,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=420863
actionName: "click",
actionIndex: 0,
events: CLICK_EVENTS,
- checkOnClickEvent: function check(aEvent) {
+ checkOnClickEvent: function check() {
// unregister click event handler
this.ID.removeEventListener("click", gClickHandler);
diff --git a/accessible/tests/mochitest/text.js b/accessible/tests/mochitest/text.js
index 21392336a1..6fe2a00b83 100644
--- a/accessible/tests/mochitest/text.js
+++ b/accessible/tests/mochitest/text.js
@@ -197,9 +197,17 @@ function testTextAfterOffset(
aBoundaryType,
aText,
aStartOffset,
- aEndOffset
+ aEndOffset,
+ ...aArgs
) {
- testTextSuperHelper("getTextAfterOffset", arguments);
+ testTextSuperHelper("getTextAfterOffset", [
+ aOffset,
+ aBoundaryType,
+ aText,
+ aStartOffset,
+ aEndOffset,
+ ...aArgs,
+ ]);
}
/**
@@ -261,9 +269,17 @@ function testTextBeforeOffset(
aBoundaryType,
aText,
aStartOffset,
- aEndOffset
+ aEndOffset,
+ ...aArgs
) {
- testTextSuperHelper("getTextBeforeOffset", arguments);
+ testTextSuperHelper("getTextBeforeOffset", [
+ aOffset,
+ aBoundaryType,
+ aText,
+ aStartOffset,
+ aEndOffset,
+ ...aArgs,
+ ]);
}
/**
diff --git a/accessible/tests/mochitest/tree/test_aria_display_contents.html b/accessible/tests/mochitest/tree/test_aria_display_contents.html
index 5c6f7f20fb..454e4e46e1 100644
--- a/accessible/tests/mochitest/tree/test_aria_display_contents.html
+++ b/accessible/tests/mochitest/tree/test_aria_display_contents.html
@@ -17,7 +17,7 @@
// Test ARIA grids that have display: contents; on different elements.
// They should all have equivalent trees.
var accTree =
- { TABLE: [
+ { GRID: [
{ ROW: [
{ role: ROLE_COLUMNHEADER,
children: [ { TEXT_LEAF: [ ] }, ]
diff --git a/accessible/tests/mochitest/tree/test_aria_grid.html b/accessible/tests/mochitest/tree/test_aria_grid.html
index 80ff97095b..4dd30e4183 100644
--- a/accessible/tests/mochitest/tree/test_aria_grid.html
+++ b/accessible/tests/mochitest/tree/test_aria_grid.html
@@ -18,7 +18,7 @@
// grid having rowgroups
var accTree =
- { TABLE: [
+ { GRID: [
{ GROUPING: [
{ ROW: [
{ GRID_CELL: [
@@ -34,7 +34,7 @@
// strange grids (mix of ARIA and HTML tables)
accTree = {
- role: ROLE_TABLE,
+ role: ROLE_GRID,
children: [
{ // div@role="row"
role: ROLE_ROW,
@@ -67,7 +67,7 @@
testAccessibleTree("strange_grid1", accTree);
accTree = {
- role: ROLE_TABLE,
+ role: ROLE_GRID,
children: [
{ // tr@role="row"
role: ROLE_ROW,
@@ -95,7 +95,7 @@
testAccessibleTree("strange_grid2", accTree);
accTree = {
- role: ROLE_TABLE,
+ role: ROLE_GRID,
children: [
{ // div@role="row"
role: ROLE_ROW,
@@ -122,7 +122,7 @@
testAccessibleTree("strange_grid3", accTree);
accTree = {
- role: ROLE_TABLE,
+ role: ROLE_GRID,
children: [
{ // div@role="row"
role: ROLE_ROW,
@@ -192,7 +192,7 @@
// grids that could contain text container accessibles but shouldn't.
accTree =
- { TABLE: [
+ { GRID: [
{ ROW: [
{ GRID_CELL: [
{ TEXT_LEAF: [ ] },
diff --git a/accessible/tests/mochitest/tree/test_aria_owns.html b/accessible/tests/mochitest/tree/test_aria_owns.html
index a01968521b..760cf7565b 100644
--- a/accessible/tests/mochitest/tree/test_aria_owns.html
+++ b/accessible/tests/mochitest/tree/test_aria_owns.html
@@ -96,7 +96,7 @@
testAccessibleTree("ariaowns_container", tree);
tree =
- { TABLE: [
+ { GRID: [
{ ROW: [
{ GRID_CELL: [
{ TEXT_LEAF: [] },
@@ -160,7 +160,7 @@
<!-- natural and aria-owns hierarchy -->
<div id="t5_2" role="note"><div aria-owns="t5_3" role="heading"></div></div>
<div id="t5_1"><div aria-owns="t5_2" role="group"></div></div>
- <div id="t5_3" role="form"><div aria-owns="t5_1" role="tooltip"></div></div>
+ <div id="t5_3" aria-label="form" role="form"><div aria-owns="t5_1" role="tooltip"></div></div>
<!-- rearrange children -->
<div id="t6_1" aria-owns="t6_3 t6_2">
diff --git a/accessible/tests/mochitest/tree/test_tabbrowser.xhtml b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml
index 401ea1a2b1..3ccf83e4dc 100644
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xhtml
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml
@@ -42,7 +42,7 @@
browserDocument().documentElement.getBoundingClientRect();
}
- this.finalCheck = function testTabHierarchy_finalCheck(aEvent)
+ this.finalCheck = function testTabHierarchy_finalCheck()
{
////////////////////
// Tab bar
diff --git a/accessible/tests/mochitest/tree/test_tree.xhtml b/accessible/tests/mochitest/tree/test_tree.xhtml
index e22b3faa9d..e70ef134e9 100644
--- a/accessible/tests/mochitest/tree/test_tree.xhtml
+++ b/accessible/tests/mochitest/tree/test_tree.xhtml
@@ -97,7 +97,7 @@
{
this.DOMNode.view = aView;
}
- this.check = function check(aEvent)
+ this.check = function check()
{
testAccessibleTreeFor(this.DOMNode, aRole);
}
diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml
index f81d77332d..9edac19af6 100644
--- a/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml
+++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml
@@ -31,7 +31,7 @@
getNode(aID).openPopup(button, "after_start", 0, 0, true, false);
}
- this.finalCheck = function openMenu_finalCheck(aEvent)
+ this.finalCheck = function openMenu_finalCheck()
{
testAccessibleTree(aID, aTree);
}
@@ -70,7 +70,7 @@
synthesizeKey("KEY_Enter");
}
- this.finalCheck = function openSubMenu_finalCheck(aEvent)
+ this.finalCheck = function openSubMenu_finalCheck()
{
testAccessibleTree(aMenuID, aTree);
}
diff --git a/accessible/tests/mochitest/treeupdate/test_delayed_removal.html b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
index 3f421f0c5b..2ba1e31203 100644
--- a/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
+++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html
@@ -218,7 +218,7 @@
}
// Check to see that a reframed body gets its children pruned correctly.
- async function bodyReframe(argument) {
+ async function bodyReframe() {
// Load sub-document in iframe.
let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe");
getNode("iframe").src =
diff --git a/accessible/tests/mochitest/treeupdate/test_shadow_slots.html b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
index 27baef0e4a..6680971401 100644
--- a/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
+++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
@@ -55,7 +55,7 @@
async function changeSlotFlat() {
await _dynamicShadowTest("changeSlotFlat",
- (container, host) => {
+ (container) => {
container.querySelector('.red').removeAttribute('slot');
container.querySelector('.green').setAttribute('slot', 'myslot');
}, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
@@ -63,7 +63,7 @@
async function changeSlotOneDeep() {
await _dynamicShadowTest("changeSlotOneDeep",
- (container, host) => {
+ (container) => {
container.querySelector('.red').removeAttribute('slot');
container.querySelector('.green').setAttribute('slot', 'myslot');
}, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]);
@@ -72,7 +72,7 @@
// Nested roots and slots
async function changeSlotNested() {
await _dynamicShadowTest("changeSlotNested",
- (container, host) => {
+ (container) => {
testAccessibleTree(getNode("changeSlotNested"),
{ SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] });
container.querySelector('.red').removeAttribute('slot');
@@ -82,14 +82,14 @@
async function changeSlotSingleChild() {
await _dynamicShadowTest("changeSlotSingleChild",
- (container, host) => {
+ (container) => {
container.querySelector('.red').setAttribute('slot', 'invalid');
}, { SECTION: [{ SECTION: [] }] });
}
async function changeSlotNoShadow() {
await _dynamicShadowTest("changeSlotNoShadow",
- (container, host) => {
+ (container) => {
// Make sure changing the slot attribute doesn't remove an element
// even when it remains in the flat tree.
container.querySelector('.red').setAttribute('slot', 'invalid');
diff --git a/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml
index ad8aebf812..9be24cb6f4 100644
--- a/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml
+++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml
@@ -57,7 +57,7 @@
getNode(aID).remove();
};
- this.check = function check(aEvent)
+ this.check = function check()
{
testIsDefunct(this.tree, aID);
testIsDefunct(this.lastItem, "last item of " + aID);
diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js
index 5dc3b595d8..6b50e5fecc 100644
--- a/accessible/tests/mochitest/treeview.js
+++ b/accessible/tests/mochitest/treeview.js
@@ -91,11 +91,11 @@ nsTreeView.prototype = {
return data.text + aCol.id;
},
- getCellValue: function getCellValue(aRow, aCol) {
+ getCellValue: function getCellValue(aRow) {
var data = this.getDataForIndex(aRow);
return data.value;
},
- getRowProperties: function getRowProperties(aIndex) {
+ getRowProperties: function getRowProperties() {
return "";
},
getCellProperties: function getCellProperties(aIndex, aCol) {
@@ -106,19 +106,19 @@ nsTreeView.prototype = {
var data = this.getDataForIndex(aIndex);
return this.mCyclerStates[data.cyclerState];
},
- getColumnProperties: function getColumnProperties(aCol) {
+ getColumnProperties: function getColumnProperties() {
return "";
},
getParentIndex: function getParentIndex(aRowIndex) {
var info = this.getInfoByIndex(aRowIndex);
return info.parentIndex;
},
- hasNextSibling: function hasNextSibling(aRowIndex, aAfterIndex) {},
+ hasNextSibling: function hasNextSibling() {},
getLevel: function getLevel(aIndex) {
var info = this.getInfoByIndex(aIndex);
return info.level;
},
- getImageSrc: function getImageSrc(aRow, aCol) {},
+ getImageSrc: function getImageSrc() {},
isContainer: function isContainer(aIndex) {
var data = this.getDataForIndex(aIndex);
return data.open != undefined;
@@ -131,7 +131,7 @@ nsTreeView.prototype = {
var data = this.getDataForIndex(aIndex);
return data.open == undefined;
},
- isSeparator: function isSeparator(aIndex) {},
+ isSeparator: function isSeparator() {},
isSorted: function isSorted() {},
toggleOpenState: function toggleOpenState(aIndex) {
var data = this.getDataForIndex(aIndex);
@@ -146,14 +146,14 @@ nsTreeView.prototype = {
}
},
selectionChanged: function selectionChanged() {},
- cycleHeader: function cycleHeader(aCol) {},
+ cycleHeader: function cycleHeader() {},
cycleCell: function cycleCell(aRow, aCol) {
var data = this.getDataForIndex(aRow);
data.cyclerState = (data.cyclerState + 1) % 3;
this.mTree.invalidateCell(aRow, aCol);
},
- isEditable: function isEditable(aRow, aCol) {
+ isEditable: function isEditable() {
return true;
},
setCellText: function setCellText(aRow, aCol, aValue) {
diff --git a/accessible/windows/ia2/ia2Accessible.cpp b/accessible/windows/ia2/ia2Accessible.cpp
index 2092730932..7318dd5e6f 100644
--- a/accessible/windows/ia2/ia2Accessible.cpp
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -156,7 +156,7 @@ ia2Accessible::role(long* aRole) {
if (!acc) return CO_E_OBJNOTCONNECTED;
#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- msaaRole, ia2Role, androidClass, nameRule) \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::_geckoRole: \
*aRole = ia2Role; \
break;
diff --git a/accessible/windows/ia2/ia2AccessibleAction.cpp b/accessible/windows/ia2/ia2AccessibleAction.cpp
index d89bd79ce1..1e1dbaea7b 100644
--- a/accessible/windows/ia2/ia2AccessibleAction.cpp
+++ b/accessible/windows/ia2/ia2AccessibleAction.cpp
@@ -11,6 +11,7 @@
#include "AccessibleWrap.h"
#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
using namespace mozilla::a11y;
diff --git a/accessible/windows/ia2/ia2AccessibleComponent.cpp b/accessible/windows/ia2/ia2AccessibleComponent.cpp
index 9c22a66cad..a0074bcaa1 100644
--- a/accessible/windows/ia2/ia2AccessibleComponent.cpp
+++ b/accessible/windows/ia2/ia2AccessibleComponent.cpp
@@ -12,6 +12,7 @@
#include "AccessibleWrap.h"
#include "States.h"
#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
#include "nsIFrame.h"
diff --git a/accessible/windows/ia2/ia2AccessibleHyperlink.cpp b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
index 9d71cfb411..40dff39131 100644
--- a/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
+++ b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
@@ -5,11 +5,14 @@
* 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 "ia2AccessibleHyperlink.h"
+
#include "AccessibleHyperlink.h"
#include "AccessibleHyperlink_i.c"
#include "AccessibleWrap.h"
#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
#include "nsIURI.h"
using namespace mozilla::a11y;
diff --git a/accessible/windows/ia2/ia2AccessibleValue.cpp b/accessible/windows/ia2/ia2AccessibleValue.cpp
index b0b9a729e3..93640354c2 100644
--- a/accessible/windows/ia2/ia2AccessibleValue.cpp
+++ b/accessible/windows/ia2/ia2AccessibleValue.cpp
@@ -12,6 +12,7 @@
#include "AccessibleWrap.h"
#include "LocalAccessible-inl.h"
#include "IUnknownImpl.h"
+#include "MsaaAccessible.h"
#include "mozilla/FloatingPoint.h"
diff --git a/accessible/windows/ia2/moz.build b/accessible/windows/ia2/moz.build
index 53f930fd5a..4b566f5a5c 100644
--- a/accessible/windows/ia2/moz.build
+++ b/accessible/windows/ia2/moz.build
@@ -44,6 +44,7 @@ LOCAL_INCLUDES += [
"/accessible/html",
"/accessible/windows",
"/accessible/windows/msaa",
+ "/accessible/windows/uia",
"/accessible/xpcom",
"/accessible/xul",
]
diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp
index abab007775..d681e8e658 100644
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -31,6 +31,8 @@ using namespace mozilla::a11y;
AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
: LocalAccessible(aContent, aDoc) {}
+AccessibleWrap::~AccessibleWrap() = default;
+
NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, LocalAccessible)
void AccessibleWrap::Shutdown() {
@@ -75,6 +77,24 @@ bool AccessibleWrap::IsRootForHWND() {
return thisHwnd != parentHwnd;
}
+static void UpdateSystemCaretForHwnd(HWND aCaretWnd,
+ const LayoutDeviceIntRect& aCaretRect) {
+ if (!aCaretWnd || aCaretRect.IsEmpty()) {
+ return;
+ }
+
+ // Create invisible bitmap for caret, otherwise its appearance interferes
+ // with Gecko caret
+ nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.Height(), 1, 1, nullptr));
+ if (::CreateCaret(aCaretWnd, caretBitMap, 1,
+ aCaretRect.Height())) { // Also destroys the last caret
+ ::ShowCaret(aCaretWnd);
+ POINT clientPoint{aCaretRect.X(), aCaretRect.Y()};
+ ::ScreenToClient(aCaretWnd, &clientPoint);
+ ::SetCaretPos(clientPoint.x, clientPoint.y);
+ }
+}
+
/* static */
void AccessibleWrap::UpdateSystemCaretFor(
Accessible* aAccessible, const LayoutDeviceIntRect& aCaretRect) {
@@ -106,7 +126,7 @@ void AccessibleWrap::UpdateSystemCaretFor(LocalAccessible* aAccessible) {
HWND caretWnd =
reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
- UpdateSystemCaretFor(caretWnd, caretRect);
+ UpdateSystemCaretForHwnd(caretWnd, caretRect);
}
/* static */
@@ -117,24 +137,5 @@ void AccessibleWrap::UpdateSystemCaretFor(
// The HWND should be the real widget HWND, not an emulated HWND.
// We get the HWND from the proxy's outer doc to bypass window emulation.
LocalAccessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
- UpdateSystemCaretFor(MsaaAccessible::GetHWNDFor(outerDoc), aCaretRect);
-}
-
-/* static */
-void AccessibleWrap::UpdateSystemCaretFor(
- HWND aCaretWnd, const LayoutDeviceIntRect& aCaretRect) {
- if (!aCaretWnd || aCaretRect.IsEmpty()) {
- return;
- }
-
- // Create invisible bitmap for caret, otherwise its appearance interferes
- // with Gecko caret
- nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.Height(), 1, 1, nullptr));
- if (::CreateCaret(aCaretWnd, caretBitMap, 1,
- aCaretRect.Height())) { // Also destroys the last caret
- ::ShowCaret(aCaretWnd);
- POINT clientPoint{aCaretRect.X(), aCaretRect.Y()};
- ::ScreenToClient(aCaretWnd, &clientPoint);
- ::SetCaretPos(clientPoint.x, clientPoint.y);
- }
+ UpdateSystemCaretForHwnd(MsaaAccessible::GetHWNDFor(outerDoc), aCaretRect);
}
diff --git a/accessible/windows/msaa/AccessibleWrap.h b/accessible/windows/msaa/AccessibleWrap.h
index aeeea87e77..941ac06e40 100644
--- a/accessible/windows/msaa/AccessibleWrap.h
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -9,7 +9,6 @@
#include "nsCOMPtr.h"
#include "LocalAccessible.h"
-#include "MsaaAccessible.h"
#include "mozilla/a11y/RemoteAccessible.h"
#include "mozilla/Attributes.h"
#include "mozilla/mscom/Utils.h"
@@ -20,6 +19,7 @@
namespace mozilla {
namespace a11y {
class DocRemoteAccessibleWrap;
+class MsaaAccessible;
/**
* Windows specific functionality for an accessibility tree node that originated
@@ -50,10 +50,6 @@ class AccessibleWrap : public LocalAccessible {
static void UpdateSystemCaretFor(RemoteAccessible* aProxy,
const LayoutDeviceIntRect& aCaretRect);
- private:
- static void UpdateSystemCaretFor(HWND aCaretWnd,
- const LayoutDeviceIntRect& aCaretRect);
-
public:
/**
* Determine whether this is the root accessible for its HWND.
@@ -64,7 +60,7 @@ class AccessibleWrap : public LocalAccessible {
virtual void GetNativeInterface(void** aOutAccessible) override;
protected:
- virtual ~AccessibleWrap() = default;
+ virtual ~AccessibleWrap();
RefPtr<MsaaAccessible> mMsaa;
};
diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.cpp b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
index 6a10a8bddc..ea0689a694 100644
--- a/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
+++ b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
@@ -11,6 +11,7 @@
#include "AccAttributes.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/Components.h"
+#include "MsaaAccessible.h"
using namespace mozilla;
using namespace mozilla::a11y;
diff --git a/accessible/windows/msaa/Compatibility.cpp b/accessible/windows/msaa/Compatibility.cpp
index 440f327520..42bc2386bb 100644
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -103,7 +103,7 @@ void Compatibility::Init() {
// Note we collect some AT statistics/telemetry here for convenience.
InitConsumers();
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::AccessibilityInProcClient,
nsPrintfCString("0x%X", sConsumers));
@@ -142,9 +142,20 @@ void Compatibility::GetHumanReadableConsumersStr(nsAString& aResult) {
}
}
-// Time when SuppressA11yForClipboardCopy() was called, as returned by
-// ::GetTickCount().
-static DWORD sA11yClipboardCopySuppressionStartTime = 0;
+struct SuppressionTimer {
+ constexpr SuppressionTimer() = default;
+ void Start() { mStart = ::GetTickCount(); }
+ bool IsActive(DWORD aTickCount) const {
+ return mStart && aTickCount - mStart < kSuppressTimeout;
+ }
+ // Last time Start() was called, as returned by ::GetTickCount().
+ DWORD mStart = 0;
+
+ static constexpr DWORD kSuppressTimeout = 1500; // ms
+};
+
+static SuppressionTimer sClipboardSuppressionTimer;
+static SuppressionTimer sSnapLayoutsSuppressionTimer;
/* static */
void Compatibility::SuppressA11yForClipboardCopy() {
@@ -166,16 +177,40 @@ void Compatibility::SuppressA11yForClipboardCopy() {
}();
if (doSuppress) {
- sA11yClipboardCopySuppressionStartTime = ::GetTickCount();
+ sClipboardSuppressionTimer.Start();
}
}
/* static */
-bool Compatibility::IsA11ySuppressedForClipboardCopy() {
- constexpr DWORD kSuppressTimeout = 1500; // ms
- if (!sA11yClipboardCopySuppressionStartTime) {
- return false;
+void Compatibility::SuppressA11yForSnapLayouts() {
+ // Bug 1883132: Snap Layouts might enable a11y to find the maximize button
+ // position.
+ bool doSuppress = [&] {
+ switch (StaticPrefs::accessibility_windows_suppress_for_snap_layout()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ // Our workaround for Snap Layouts is needed from Windows 11 22H2
+ return IsWin1122H2OrLater();
+ }
+ }();
+
+ if (doSuppress) {
+ sSnapLayoutsSuppressionTimer.Start();
+ }
+}
+
+/* static */
+SuppressionReasons Compatibility::A11ySuppressionReasons() {
+ const auto now = ::GetTickCount();
+ auto reasons = SuppressionReasons::None;
+ if (sClipboardSuppressionTimer.IsActive(now)) {
+ reasons |= SuppressionReasons::Clipboard;
+ }
+ if (sSnapLayoutsSuppressionTimer.IsActive(now)) {
+ reasons |= SuppressionReasons::SnapLayouts;
}
- return ::GetTickCount() - sA11yClipboardCopySuppressionStartTime <
- kSuppressTimeout;
+ return reasons;
}
diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h
index d1e1b0e9ac..05d2e82400 100644
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -13,8 +13,14 @@
#include <stdint.h>
-namespace mozilla {
-namespace a11y {
+namespace mozilla::a11y {
+
+enum class SuppressionReasons : uint8_t {
+ None = 0,
+ Clipboard = 1 << 0,
+ SnapLayouts = 1 << 1,
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SuppressionReasons);
/**
* Used to get compatibility modes. Note, modes are computed at accessibility
@@ -78,7 +84,11 @@ class Compatibility {
unsigned long long aVersion);
static void SuppressA11yForClipboardCopy();
- static bool IsA11ySuppressedForClipboardCopy();
+ static void SuppressA11yForSnapLayouts();
+ static bool IsA11ySuppressed() {
+ return A11ySuppressionReasons() != SuppressionReasons::None;
+ }
+ static SuppressionReasons A11ySuppressionReasons();
private:
Compatibility();
@@ -112,8 +122,7 @@ class Compatibility {
static uint32_t sConsumers;
};
-} // namespace a11y
-} // namespace mozilla
+} // namespace mozilla::a11y
// Convert the 4 (decimal) components of a DLL version number into a
// single unsigned long long, as needed by
diff --git a/accessible/windows/msaa/LazyInstantiator.cpp b/accessible/windows/msaa/LazyInstantiator.cpp
index 5f8ae9a8a4..61814d059a 100644
--- a/accessible/windows/msaa/LazyInstantiator.cpp
+++ b/accessible/windows/msaa/LazyInstantiator.cpp
@@ -249,11 +249,7 @@ bool LazyInstantiator::ShouldInstantiate(const DWORD aClientPid) {
* various different types of clients.
*/
bool LazyInstantiator::ShouldInstantiate() {
- if (Compatibility::IsA11ySuppressedForClipboardCopy()) {
- // Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2)
- // walks the entire a11y tree using UIA whenever anything is copied to the
- // clipboard. This causes an unacceptable hang, particularly when the cache
- // is disabled. Don't allow a11y to be instantiated by this.
+ if (Compatibility::IsA11ySuppressed()) {
return false;
}
if (DWORD pid = GetRemoteMsaaClientPid()) {
diff --git a/accessible/windows/msaa/MsaaAccessible.cpp b/accessible/windows/msaa/MsaaAccessible.cpp
index 702f8341dc..3a0e8fc726 100644
--- a/accessible/windows/msaa/MsaaAccessible.cpp
+++ b/accessible/windows/msaa/MsaaAccessible.cpp
@@ -522,6 +522,10 @@ MsaaAccessible::QueryInterface(REFIID iid, void** ppv) {
if (SUCCEEDED(hr)) {
return hr;
}
+ hr = uiaRawElmProvider::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr)) {
+ return hr;
+ }
}
if (*ppv) {
(reinterpret_cast<IUnknown*>(*ppv))->AddRef();
@@ -604,16 +608,20 @@ MsaaAccessible::get_accChildCount(long __RPC_FAR* pcountChildren) {
if (!mAcc) return CO_E_OBJNOTCONNECTED;
- if (Compatibility::IsA11ySuppressedForClipboardCopy() && mAcc->IsRoot()) {
+ if ((Compatibility::A11ySuppressionReasons() &
+ SuppressionReasons::Clipboard) &&
+ mAcc->IsRoot()) {
// Bug 1798098: Windows Suggested Actions (introduced in Windows 11 22H2)
- // might walk the entire a11y tree using UIA whenever anything is copied to
- // the clipboard. This causes an unacceptable hang, particularly when the
- // cache is disabled. We prevent this tree walk by returning a 0 child count
- // for the root window, from which Windows might walk.
+ // might walk the entire a11y tree using UIA whenever anything is copied
+ // to the clipboard. This causes an unacceptable hang, particularly when
+ // the cache is disabled. We prevent this tree walk by returning a 0 child
+ // count for the root window, from which Windows might walk.
return S_OK;
}
- if (nsAccUtils::MustPrune(mAcc)) return S_OK;
+ if (nsAccUtils::MustPrune(mAcc)) {
+ return S_OK;
+ }
*pcountChildren = mAcc->ChildCount();
return S_OK;
@@ -761,7 +769,7 @@ MsaaAccessible::get_accRole(
uint32_t msaaRole = 0;
#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
- _msaaRole, ia2Role, androidClass, nameRule) \
+ _msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
case roles::_geckoRole: \
msaaRole = _msaaRole; \
break;
diff --git a/accessible/windows/msaa/MsaaAccessible.h b/accessible/windows/msaa/MsaaAccessible.h
index 034db860ed..6d6582f010 100644
--- a/accessible/windows/msaa/MsaaAccessible.h
+++ b/accessible/windows/msaa/MsaaAccessible.h
@@ -12,8 +12,9 @@
#include "ia2AccessibleHyperlink.h"
#include "ia2AccessibleValue.h"
#include "IUnknownImpl.h"
-#include "mozilla/a11y/MsaaIdGenerator.h"
+#include "MsaaIdGenerator.h"
#include "nsXULAppAPI.h"
+#include "uiaRawElmProvider.h"
namespace mozilla {
namespace a11y {
@@ -25,7 +26,8 @@ class sdnAccessible;
class MsaaAccessible : public ia2Accessible,
public ia2AccessibleComponent,
public ia2AccessibleHyperlink,
- public ia2AccessibleValue {
+ public ia2AccessibleValue,
+ public uiaRawElmProvider {
public:
static MsaaAccessible* Create(Accessible* aAcc);
diff --git a/accessible/windows/msaa/MsaaIdGenerator.cpp b/accessible/windows/msaa/MsaaIdGenerator.cpp
index 9f220365e3..8f0040cf02 100644
--- a/accessible/windows/msaa/MsaaIdGenerator.cpp
+++ b/accessible/windows/msaa/MsaaIdGenerator.cpp
@@ -6,11 +6,11 @@
#include "MsaaIdGenerator.h"
-#include "mozilla/a11y/MsaaAccessible.h"
#include "mozilla/Assertions.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Unused.h"
+#include "MsaaAccessible.h"
#include "nsAccessibilityService.h"
#include "sdnAccessible.h"
diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp
index 40dddac215..018042c5d3 100644
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -9,6 +9,7 @@
#include "AccEvent.h"
#include "Compatibility.h"
#include "HyperTextAccessible.h"
+#include "MsaaAccessible.h"
#include "nsWinUtils.h"
#include "mozilla/a11y/DocAccessibleParent.h"
#include "mozilla/a11y/RemoteAccessible.h"
@@ -196,9 +197,8 @@ static void AccumulateInstantiatorTelemetry(const nsAString& aValue) {
#if defined(MOZ_TELEMETRY_REPORTING)
Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS, aValue);
#endif // defined(MOZ_TELEMETRY_REPORTING)
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::AccessibilityClient,
- NS_ConvertUTF16toUTF8(aValue));
+ CrashReporter::RecordAnnotationNSString(
+ CrashReporter::Annotation::AccessibilityClient, aValue);
}
}
diff --git a/accessible/windows/msaa/ServiceProvider.cpp b/accessible/windows/msaa/ServiceProvider.cpp
index b2b005f3b4..ba04d0ecde 100644
--- a/accessible/windows/msaa/ServiceProvider.cpp
+++ b/accessible/windows/msaa/ServiceProvider.cpp
@@ -16,7 +16,7 @@
#include "uiaRawElmProvider.h"
#include "mozilla/a11y/DocAccessibleChild.h"
-#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_accessibility.h"
#include "ISimpleDOM.h"
@@ -40,17 +40,6 @@ ServiceProvider::QueryService(REFGUID aGuidService, REFIID aIID,
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
- AccessibleWrap* localAcc = mMsaa->LocalAcc();
-
- // UIA IAccessibleEx
- if (aGuidService == IID_IAccessibleEx &&
- Preferences::GetBool("accessibility.uia.enable") && localAcc) {
- uiaRawElmProvider* accEx = new uiaRawElmProvider(localAcc);
- HRESULT hr = accEx->QueryInterface(aIID, aInstancePtr);
- if (FAILED(hr)) delete accEx;
-
- return hr;
- }
// Provide a special service ID for getting the accessible for the browser tab
// document that contains this accessible object. If this accessible object
@@ -96,8 +85,12 @@ ServiceProvider::QueryService(REFGUID aGuidService, REFIID aIID,
{0xb6, 0x61, 0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8}};
if (aGuidService == IID_ISimpleDOMNode ||
aGuidService == IID_SimpleDOMDeprecated ||
- aGuidService == IID_IAccessible || aGuidService == IID_IAccessible2)
+ aGuidService == IID_IAccessible || aGuidService == IID_IAccessible2 ||
+ // UIA IAccessibleEx
+ (aGuidService == IID_IAccessibleEx &&
+ StaticPrefs::accessibility_uia_enable())) {
return mMsaa->QueryInterface(aIID, aInstancePtr);
+ }
return E_INVALIDARG;
}
diff --git a/accessible/windows/msaa/moz.build b/accessible/windows/msaa/moz.build
index 1b56c2eacf..ae5a036ec2 100644
--- a/accessible/windows/msaa/moz.build
+++ b/accessible/windows/msaa/moz.build
@@ -12,8 +12,6 @@ EXPORTS.mozilla.a11y += [
"AccessibleWrap.h",
"Compatibility.h",
"LazyInstantiator.h",
- "MsaaAccessible.h",
- "MsaaIdGenerator.h",
"nsWinUtils.h",
]
diff --git a/accessible/windows/msaa/nsWinUtils.cpp b/accessible/windows/msaa/nsWinUtils.cpp
index 6eb7d290dd..42d3d374f7 100644
--- a/accessible/windows/msaa/nsWinUtils.cpp
+++ b/accessible/windows/msaa/nsWinUtils.cpp
@@ -9,6 +9,7 @@
#include "Compatibility.h"
#include "DocAccessible.h"
+#include "MsaaAccessible.h"
#include "nsAccessibilityService.h"
#include "nsCoreUtils.h"
diff --git a/accessible/windows/sdn/moz.build b/accessible/windows/sdn/moz.build
index 8d76b7482e..4f7b77efad 100644
--- a/accessible/windows/sdn/moz.build
+++ b/accessible/windows/sdn/moz.build
@@ -15,6 +15,7 @@ LOCAL_INCLUDES += [
"/accessible/generic",
"/accessible/html",
"/accessible/windows/msaa",
+ "/accessible/windows/uia",
"/accessible/xpcom",
"/accessible/xul",
]
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
index c78dbca7fe..881ed22277 100644
--- a/accessible/windows/uia/uiaRawElmProvider.cpp
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -10,6 +10,9 @@
#include "AccessibleWrap.h"
#include "ARIAMap.h"
#include "LocalAccessible-inl.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "MsaaAccessible.h"
+#include "nsTextEquivUtils.h"
using namespace mozilla;
using namespace mozilla::a11y;
@@ -18,6 +21,23 @@ using namespace mozilla::a11y;
// uiaRawElmProvider
////////////////////////////////////////////////////////////////////////////////
+Accessible* uiaRawElmProvider::Acc() {
+ return static_cast<MsaaAccessible*>(this)->Acc();
+}
+
+// IUnknown
+
+// Because uiaRawElmProvider inherits multiple COM interfaces (and thus multiple
+// IUnknowns), we need to explicitly implement AddRef and Release to make
+// our QueryInterface implementation (IMPL_IUNKNOWN2) happy.
+ULONG STDMETHODCALLTYPE uiaRawElmProvider::AddRef() {
+ return static_cast<MsaaAccessible*>(this)->AddRef();
+}
+
+ULONG STDMETHODCALLTYPE uiaRawElmProvider::Release() {
+ return static_cast<MsaaAccessible*>(this)->Release();
+}
+
IMPL_IUNKNOWN2(uiaRawElmProvider, IAccessibleEx, IRawElementProviderSimple)
////////////////////////////////////////////////////////////////////////////////
@@ -30,7 +50,7 @@ uiaRawElmProvider::GetObjectForChild(
*aAccEx = nullptr;
- return mAcc->IsDefunct() ? CO_E_OBJNOTCONNECTED : S_OK;
+ return Acc() ? S_OK : CO_E_OBJNOTCONNECTED;
}
STDMETHODIMP
@@ -41,11 +61,12 @@ uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc,
*aAcc = nullptr;
*aIdChild = 0;
- if (mAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED;
+ if (!Acc()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
*aIdChild = CHILDID_SELF;
- RefPtr<IAccessible> copy;
- mAcc->GetNativeInterface(getter_AddRefs(copy));
+ RefPtr<IAccessible> copy = static_cast<MsaaAccessible*>(this);
copy.forget(aAcc);
return S_OK;
@@ -54,9 +75,12 @@ uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc,
STDMETHODIMP
uiaRawElmProvider::GetRuntimeId(__RPC__deref_out_opt SAFEARRAY** aRuntimeIds) {
if (!aRuntimeIds) return E_INVALIDARG;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
- int ids[] = {UiaAppendRuntimeId,
- static_cast<int>(reinterpret_cast<intptr_t>(mAcc->UniqueID()))};
+ int ids[] = {UiaAppendRuntimeId, MsaaAccessible::GetChildIDFor(acc)};
*aRuntimeIds = SafeArrayCreateVector(VT_I4, 0, 2);
if (!*aRuntimeIds) return E_OUTOFMEMORY;
@@ -108,16 +132,24 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
__RPC__out VARIANT* aPropertyValue) {
if (!aPropertyValue) return E_INVALIDARG;
- if (mAcc->IsDefunct()) return CO_E_OBJNOTCONNECTED;
+ Accessible* acc = Acc();
+ if (!acc) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+ LocalAccessible* localAcc = acc->AsLocal();
aPropertyValue->vt = VT_EMPTY;
switch (aPropertyId) {
// Accelerator Key / shortcut.
case UIA_AcceleratorKeyPropertyId: {
+ if (!localAcc) {
+ // KeyboardShortcut is only currently relevant for LocalAccessible.
+ break;
+ }
nsAutoString keyString;
- mAcc->KeyboardShortcut().ToString(keyString);
+ localAcc->KeyboardShortcut().ToString(keyString);
if (!keyString.IsEmpty()) {
aPropertyValue->vt = VT_BSTR;
@@ -132,7 +164,7 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
case UIA_AccessKeyPropertyId: {
nsAutoString keyString;
- mAcc->AccessKey().ToString(keyString);
+ acc->AccessKey().ToString(keyString);
if (!keyString.IsEmpty()) {
aPropertyValue->vt = VT_BSTR;
@@ -147,7 +179,7 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
case UIA_AriaRolePropertyId: {
nsAutoString xmlRoles;
- RefPtr<AccAttributes> attributes = mAcc->Attributes();
+ RefPtr<AccAttributes> attributes = acc->Attributes();
attributes->GetAttribute(nsGkAtoms::xmlroles, xmlRoles);
if (!xmlRoles.IsEmpty()) {
@@ -161,9 +193,16 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
// ARIA Properties
case UIA_AriaPropertiesPropertyId: {
+ if (!localAcc) {
+ // XXX Implement a unified version of this. We don't cache explicit
+ // values for many ARIA attributes in RemoteAccessible; e.g. we use the
+ // checked state rather than caching aria-checked:true. Thus, a unified
+ // implementation will need to work with State(), etc.
+ break;
+ }
nsAutoString ariaProperties;
- aria::AttrIterator attribIter(mAcc->GetContent());
+ aria::AttrIterator attribIter(localAcc->GetContent());
while (attribIter.Next()) {
nsAutoString attribName, attribValue;
nsAutoString value;
@@ -190,6 +229,12 @@ uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
break;
}
+
+ case UIA_IsControlElementPropertyId:
+ case UIA_IsContentElementPropertyId:
+ aPropertyValue->vt = VT_BOOL;
+ aPropertyValue->boolVal = IsControl() ? VARIANT_TRUE : VARIANT_FALSE;
+ return S_OK;
}
return S_OK;
@@ -204,3 +249,68 @@ uiaRawElmProvider::get_HostRawElementProvider(
*aRawElmProvider = nullptr;
return S_OK;
}
+
+// Private methods
+
+bool uiaRawElmProvider::IsControl() {
+ // UIA provides multiple views of the tree: raw, control and content. The
+ // control and content views should only contain elements which a user cares
+ // about when navigating.
+ Accessible* acc = Acc();
+ MOZ_ASSERT(acc);
+ if (acc->IsTextLeaf()) {
+ // If an ancestor control allows the name to be generated from content, do
+ // not expose this text leaf as a control. Otherwise, the user will see the
+ // text twice: once as the label of the control and once for the text leaf.
+ for (Accessible* ancestor = acc->Parent(); ancestor && !ancestor->IsDoc();
+ ancestor = ancestor->Parent()) {
+ if (nsTextEquivUtils::HasNameRule(ancestor, eNameFromSubtreeRule)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (acc->HasNumericValue() || acc->ActionCount() > 0) {
+ return true;
+ }
+ uint64_t state = acc->State();
+ if (state & states::FOCUSABLE) {
+ return true;
+ }
+ if (state & states::EDITABLE) {
+ Accessible* parent = acc->Parent();
+ if (parent && !(parent->State() & states::EDITABLE)) {
+ // This is the root of a rich editable control.
+ return true;
+ }
+ }
+
+ // Don't treat generic or text containers as controls unless they have a name
+ // or description.
+ switch (acc->Role()) {
+ case roles::EMPHASIS:
+ case roles::MARK:
+ case roles::PARAGRAPH:
+ case roles::SECTION:
+ case roles::STRONG:
+ case roles::SUBSCRIPT:
+ case roles::SUPERSCRIPT:
+ case roles::TEXT:
+ case roles::TEXT_CONTAINER: {
+ if (!acc->NameIsEmpty()) {
+ return true;
+ }
+ nsAutoString text;
+ acc->Description(text);
+ if (!text.IsEmpty()) {
+ return true;
+ }
+ return false;
+ }
+ default:
+ break;
+ }
+
+ return true;
+}
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
index 4a4aecdbe2..0e5172c805 100644
--- a/accessible/windows/uia/uiaRawElmProvider.h
+++ b/accessible/windows/uia/uiaRawElmProvider.h
@@ -8,25 +8,24 @@
#define mozilla_a11y_uiaRawElmProvider_h__
#include "objbase.h"
-#include "AccessibleWrap.h"
#include "IUnknownImpl.h"
#include "uiautomation.h"
namespace mozilla {
namespace a11y {
-class AccessibleWrap;
+class Accessible;
/**
* IRawElementProviderSimple implementation (maintains IAccessibleEx approach).
*/
-class uiaRawElmProvider final : public IAccessibleEx,
- public IRawElementProviderSimple {
+class uiaRawElmProvider : public IAccessibleEx,
+ public IRawElementProviderSimple {
public:
- explicit uiaRawElmProvider(AccessibleWrap* aAcc) : mAcc(aAcc) {}
-
// IUnknown
- DECL_IUNKNOWN
+ DECL_IUNKNOWN_INHERITED
+ ULONG STDMETHODCALLTYPE AddRef() override;
+ ULONG STDMETHODCALLTYPE Release() override;
// IAccessibleEx
virtual HRESULT STDMETHODCALLTYPE GetObjectForChild(
@@ -61,12 +60,8 @@ class uiaRawElmProvider final : public IAccessibleEx,
aRawElmProvider);
private:
- uiaRawElmProvider() = delete;
- uiaRawElmProvider& operator=(const uiaRawElmProvider&) = delete;
- uiaRawElmProvider(const uiaRawElmProvider&) = delete;
-
- protected:
- RefPtr<AccessibleWrap> mAcc;
+ Accessible* Acc();
+ bool IsControl();
};
} // namespace a11y
diff --git a/accessible/xpcom/moz.build b/accessible/xpcom/moz.build
index 2ce13081a2..65cfbfdc0b 100644
--- a/accessible/xpcom/moz.build
+++ b/accessible/xpcom/moz.build
@@ -59,6 +59,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",
diff --git a/accessible/xul/moz.build b/accessible/xul/moz.build
index 4fccfff6e0..88fc3ffa29 100644
--- a/accessible/xul/moz.build
+++ b/accessible/xul/moz.build
@@ -46,6 +46,10 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
LOCAL_INCLUDES += [
"/accessible/android",
]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ LOCAL_INCLUDES += [
+ "/accessible/ios",
+ ]
else:
LOCAL_INCLUDES += [
"/accessible/other",