Bug 1104947 - Complete the accessibility mapping for aria-current, r=Jamie
authorMarco Zehe <mzehe@mozilla.com>
Fri, 02 Nov 2018 06:40:47 +0000
changeset 500487 a6493a0b53a3
parent 500486 c498718cc824
child 500488 2a050ad70afb
child 500545 8fa378e960b1
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJamie
bugs1104947, 1365904
milestone65.0a1
first release with
nightly linux32
a6493a0b53a3 / 65.0a1 / 20181102100039 / files
nightly linux64
a6493a0b53a3 / 65.0a1 / 20181102100039 / files
nightly mac
a6493a0b53a3 / 65.0a1 / 20181102100039 / files
nightly win32
a6493a0b53a3 / 65.0a1 / 20181102100039 / files
nightly win64
a6493a0b53a3 / 65.0a1 / 20181102100039 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1104947 - Complete the accessibility mapping for aria-current, r=Jamie We already mapped aria-current to the correct attribute, but we did so unconditionally unless the value was false or undefined. However, if the value was an invalid token, it would not be mapped to "true" as per spec. Now, any bogus value for aria-current will be mapped as if the author had specified "true". In addition, this patch adds aria-current to the known set of attributes with rules, which causes NSAccessibilityService::MustBeAccessible to make sure an accessible gets created if aria-current is set, even if that accessible is a html:span element. This fixes bug 1365904. Differential Revision: https://phabricator.services.mozilla.com/D10331
accessible/base/ARIAMap.cpp
accessible/base/nsAccUtils.cpp
accessible/base/nsAccUtils.h
accessible/tests/mochitest/attributes/test_obj.html
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -1297,16 +1297,17 @@ struct AttrCharacteristics
 
 static const AttrCharacteristics gWAIUnivAttrMap[] = {
   // clang-format off
   {nsGkAtoms::aria_activedescendant,  ATTR_BYPASSOBJ                               },
   {nsGkAtoms::aria_atomic,   ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL },
   {nsGkAtoms::aria_busy,                               ATTR_VALTOKEN | ATTR_GLOBAL },
   {nsGkAtoms::aria_checked,           ATTR_BYPASSOBJ | ATTR_VALTOKEN               }, /* exposes checkable obj attr */
   {nsGkAtoms::aria_controls,          ATTR_BYPASSOBJ                 | ATTR_GLOBAL },
+  {nsGkAtoms::aria_current,  ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL },
   {nsGkAtoms::aria_describedby,       ATTR_BYPASSOBJ                 | ATTR_GLOBAL },
   {nsGkAtoms::aria_details,           ATTR_BYPASSOBJ                 | ATTR_GLOBAL },
   {nsGkAtoms::aria_disabled,          ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
   {nsGkAtoms::aria_dropeffect,                         ATTR_VALTOKEN | ATTR_GLOBAL },
   {nsGkAtoms::aria_errormessage,      ATTR_BYPASSOBJ                 | ATTR_GLOBAL },
   {nsGkAtoms::aria_expanded,          ATTR_BYPASSOBJ | ATTR_VALTOKEN               },
   {nsGkAtoms::aria_flowto,            ATTR_BYPASSOBJ                 | ATTR_GLOBAL },
   {nsGkAtoms::aria_grabbed,                            ATTR_VALTOKEN | ATTR_GLOBAL },
@@ -1469,16 +1470,25 @@ AttrIterator::Next(nsAString& aAttrName,
           mElement->AttrValueIs(kNameSpaceID_None, attrAtom,
                                 nsGkAtoms::_false, eCaseMatters)) {
         continue; // only expose token based attribute if value is not 'false'.
       }
 
       nsAutoString value;
       if (mElement->GetAttr(kNameSpaceID_None, attrAtom, value)) {
         aAttrName.Assign(Substring(attrStr, 5));
+        if (attrFlags & ATTR_VALTOKEN) {
+          nsAtom* normalizedValue =
+            nsAccUtils::NormalizeARIAToken(mElement, attrAtom);
+          if (normalizedValue) {
+            nsDependentAtomString normalizedValueStr(normalizedValue);
+            aAttrValue.Assign(normalizedValueStr);
+            return true;
+          }
+        }
         aAttrValue.Assign(value);
         return true;
       }
     }
   }
 
   return false;
 }
--- a/accessible/base/nsAccUtils.cpp
+++ b/accessible/base/nsAccUtils.cpp
@@ -220,16 +220,36 @@ nsAccUtils::GetARIAToken(dom::Element* a
   int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None,
                                           aAttr, tokens, eCaseMatters);
   if (idx >= 0)
     return tokens[idx];
 
   return nullptr;
 }
 
+nsStaticAtom*
+nsAccUtils::NormalizeARIAToken(dom::Element* aElement, nsAtom* aAttr)
+{
+  if (!HasDefinedARIAToken(aElement, aAttr)) {
+    return nsGkAtoms::_empty;
+  }
+
+  if (aAttr== nsGkAtoms::aria_current) {
+    static Element::AttrValuesArray tokens[] =
+      { nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location_,
+        nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true, nullptr};
+    int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None,
+                                    aAttr, tokens, eCaseMatters);
+    // If the token is present, return it, otherwise TRUE as per spec.
+    return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true;
+  }
+
+  return nullptr;
+}
+
 Accessible*
 nsAccUtils::GetSelectableContainer(Accessible* aAccessible, uint64_t aState)
 {
   if (!aAccessible)
     return nullptr;
 
   if (!(aState & states::SELECTABLE))
     return nullptr;
--- a/accessible/base/nsAccUtils.h
+++ b/accessible/base/nsAccUtils.h
@@ -100,16 +100,24 @@ public:
 
   /**
    * Return atomic value of ARIA attribute of boolean or NMTOKEN type.
    */
   static nsStaticAtom* GetARIAToken(mozilla::dom::Element* aElement,
                                     nsAtom* aAttr);
 
   /**
+   * If the given ARIA attribute has a specific known token value, return it.
+   * If the specification demands for a fallback value for unknown attribute 
+   * values, return that. For all others, return a nullptr.
+   */
+  static nsStaticAtom* NormalizeARIAToken(mozilla::dom::Element* aElement,
+                                          nsAtom* aAttr);
+
+  /**
    * Return document accessible for the given DOM node.
    */
   static DocAccessible* GetDocAccessibleFor(nsINode* aNode)
   {
     nsIPresShell *presShell = nsCoreUtils::GetPresShellFor(aNode);
     return GetAccService()->GetDocAccessible(presShell);
   }
 
--- a/accessible/tests/mochitest/attributes/test_obj.html
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -46,16 +46,24 @@ https://bugzilla.mozilla.org/show_bug.cg
       testAttrs("haspopupTree", { "haspopup": "tree" }, true);
       testAbsentAttrs("modal", {"modal": "true"});
       testAttrs("sortAscending", {"sort": "ascending"}, true);
       testAttrs("sortDescending", {"sort": "descending"}, true);
       testAttrs("sortNone", {"sort": "none"}, true);
       testAttrs("sortOther", {"sort": "other"}, true);
       testAttrs("roledescr", {"roledescription": "spreadshit"}, true);
       testAttrs("currentPage", {"current": "page"}, true);
+      testAttrs("currentStep", {"current": "step"}, true);
+      testAttrs("currentLocation", {"current": "location"}, true);
+      testAttrs("currentDate", {"current": "date"}, true);
+      testAttrs("currentTime", {"current": "time"}, true);
+      testAttrs("currentTrue", {"current": "true"}, true);
+      testAttrs("currentOther", {"current": "true"}, true);
+      testAbsentAttrs("currentFalse", {"current": "true"});
+      testAttrs("currentSpan", {"current": "page"}, true);
 
       // inherited attributes by subdocuments
       var subdoc = getAccessible("iframe").firstChild;
       testAttrs(subdoc, {"busy": "true"}, true);
 
       // live object attribute
 
       // HTML
@@ -182,16 +190,30 @@ https://bugzilla.mozilla.org/show_bug.cg
   <div id="haspopupTree" aria-haspopup="tree"></div>
   <div id="modal" aria-modal="true"></div>
   <div id="sortAscending" role="columnheader" aria-sort="ascending"></div>
   <div id="sortDescending" role="columnheader" aria-sort="descending"></div>
   <div id="sortNone" role="columnheader" aria-sort="none"></div>
   <div id="sortOther" role="columnheader" aria-sort="other"></div>
   <div id="roledescr" aria-roledescription="spreadshit"></div>
   <div id="currentPage" aria-current="page"></div>
+  <div id="currentStep" aria-current="step"></div>
+  <div id="currentLocation" aria-current="location"></div>
+  <div id="currentDate" aria-current="date"></div>
+  <div id="currentTime" aria-current="time"></div>
+  <div id="currentTrue" aria-current="true"></div>
+  <div id="currentOther" aria-current="other"></div>
+  <div id="currentFalse" aria-current="false"></div>
+
+  <!-- aria-current on a span which must create an accessible -->
+  <ol>
+    <li><a href="...">Page 1</a></li>
+    <li><a href="...">Page 2</a></li>
+    <li><span id="currentSpan" aria-current="page">This page</span></li>
+  </ol>
 
   <!-- inherited from iframe -->
   <iframe id="iframe" src="data:text/html,<html><body></body></html>"
           aria-busy="true"></iframe>
 
   <!-- html -->
   <output id="output"></output>