diff options
Diffstat (limited to 'accessible')
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", |