Bug 1507713 - Provide heading level in roleDescription. r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Wed, 05 Dec 2018 20:13:07 +0000
changeset 449479 5732f962288c49e1520f04abb7f2f90b1942b175
parent 449478 0bcaabfc8a1d39d96314a881d3ea9989c025a8fa
child 449480 a30f9bf957fdda96baacd07245fa4f486b1737bc
push id74295
push usereisaacson@mozilla.com
push dateWed, 05 Dec 2018 22:25:29 +0000
treeherderautoland@5732f962288c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1507713
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1507713 - Provide heading level in roleDescription. r=yzen Differential Revision: https://phabricator.services.mozilla.com/D13504
accessible/android/AccessibleWrap.cpp
accessible/android/AccessibleWrap.h
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -282,17 +282,19 @@ uint32_t AccessibleWrap::GetFlags(role a
 
   if (aRole == roles::PASSWORD_TEXT) {
     flags |= java::SessionAccessibility::FLAG_PASSWORD;
   }
 
   return flags;
 }
 
-void AccessibleWrap::GetRoleDescription(role aRole, nsAString& aGeckoRole,
+void AccessibleWrap::GetRoleDescription(role aRole,
+                                        nsIPersistentProperties* aAttributes,
+                                        nsAString& aGeckoRole,
                                         nsAString& aRoleDescription) {
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsIStringBundleService> sbs =
       do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get string bundle service");
     return;
@@ -300,16 +302,29 @@ void AccessibleWrap::GetRoleDescription(
 
   nsCOMPtr<nsIStringBundle> bundle;
   rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get string bundle");
     return;
   }
 
+  if (aRole == roles::HEADING) {
+    nsString level;
+    rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("level"), level);
+    if (NS_SUCCEEDED(rv)) {
+      const char16_t* formatString[] = {level.get()};
+      rv = bundle->FormatStringFromName("headingLevel", formatString, 1,
+                                        aRoleDescription);
+      if (NS_SUCCEEDED(rv)) {
+        return;
+      }
+    }
+  }
+
   GetAccService()->GetStringRole(aRole, aGeckoRole);
   rv = bundle->GetStringFromName(NS_ConvertUTF16toUTF8(aGeckoRole).get(),
                                  aRoleDescription);
   if (NS_FAILED(rv)) {
     aRoleDescription.AssignLiteral("");
   }
 }
 
@@ -445,17 +460,17 @@ mozilla::java::GeckoBundle::LocalRef Acc
     GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
   } else {
     GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
   }
 
   nsAutoString geckoRole;
   nsAutoString roleDescription;
   if (VirtualViewID() != kNoID) {
-    GetRoleDescription(role, geckoRole, roleDescription);
+    GetRoleDescription(role, aAttributes, geckoRole, roleDescription);
   }
 
   GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
                   jni::StringParam(roleDescription));
   GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
 
   GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
                   jni::StringParam(roleDescription));
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -76,18 +76,21 @@ class AccessibleWrap : public Accessible
     return static_cast<AccessibleWrap*>(Parent());
   }
 
   virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal,
                                 double* aMaxVal, double* aStep);
 
   virtual role WrapperRole() { return Role(); }
 
-  static void GetRoleDescription(role aRole, nsAString& aGeckoRole,
+  static void GetRoleDescription(role aRole,
+                                 nsIPersistentProperties* aAttributes,
+                                 nsAString& aGeckoRole,
                                  nsAString& aRoleDescription);
+
   static uint32_t GetFlags(role aRole, uint64_t aState, uint8_t aActionCount);
 };
 
 static inline AccessibleWrap* WrapperFor(const ProxyAccessible* aProxy) {
   return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
 }
 
 }  // namespace a11y
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -432,16 +432,74 @@ class AccessibilityTest : BaseSessionTes
         waitUntilTextTraversed(18, 28) // "sit amet, "
 
         provider.performAction(nodeId,
                 AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                 moveByGranularityArguments(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE))
         waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
     }
 
+    @Test fun testHeadings() {
+        var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
+        sessionRule.session.loadString("""
+            <a href=\"%23\">preamble</a>
+            <h1>Fried cheese</h1><p>with club sauce.</p>
+            <h2>Popcorn shrimp</h2><button>with club sauce.</button>
+            <h3>Chicken fingers</h3><p>with spicy club sauce.</p>""".trimIndent(), "text/html")
+        waitForInitialFocus()
+
+        val bundle = Bundle()
+        bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "HEADING")
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                val node = createNodeInfo(nodeId)
+                assertThat("Accessibility focus on first heading", node.text as String, startsWith("Fried cheese"))
+                if (Build.VERSION.SDK_INT >= 19) {
+                    assertThat("First heading is level 1",
+                            node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription").toString(),
+                            equalTo("heading level 1"))
+                }
+            }
+        })
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                val node = createNodeInfo(nodeId)
+                assertThat("Accessibility focus on second heading", node.text as String, startsWith("Popcorn shrimp"))
+                if (Build.VERSION.SDK_INT >= 19) {
+                    assertThat("Second heading is level 2",
+                            node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription").toString(),
+                            equalTo("heading level 2"))
+                }
+            }
+        })
+
+        provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, bundle)
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onAccessibilityFocused(event: AccessibilityEvent) {
+                nodeId = getSourceId(event)
+                val node = createNodeInfo(nodeId)
+                assertThat("Accessibility focus on second heading", node.text as String, startsWith("Chicken fingers"))
+                if (Build.VERSION.SDK_INT >= 19) {
+                    assertThat("Third heading is level 3",
+                            node.extras.getCharSequence("AccessibilityNodeInfo.roleDescription").toString(),
+                            equalTo("heading level 3"))
+                }
+            }
+        })
+    }
+
     @Test fun testCheckbox() {
         var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
         sessionRule.session.loadString("<label><input type='checkbox'>many option</label>", "text/html")
         waitForInitialFocus(true)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {