Bug 1316285 - Expose role and landmark role for HTML header and footer only if they are direct descendants of the body tag, section otherwise. r=eeejay draft
authorMarco Zehe <mzehe@mozilla.com>
Mon, 28 Aug 2017 14:30:17 +0200
changeset 655555 6f4bbb7672cf70f15ae00abeaa41b3edc02a03f5
parent 654947 1de6b40aa43e96b3cf0cb7f1d098d7cf3e046b7d
child 728870 94dc6feae76611916a76f3ee2409c318dee227fd
push id76913
push usermzehe@mozilla.com
push dateWed, 30 Aug 2017 05:52:34 +0000
reviewerseeejay
bugs1316285
milestone57.0a1
Bug 1316285 - Expose role and landmark role for HTML header and footer only if they are direct descendants of the body tag, section otherwise. r=eeejay MozReview-Commit-ID: LbelxxgHlJ6
accessible/base/MarkupMap.h
accessible/base/nsAccessibilityService.cpp
accessible/generic/HyperTextAccessible.cpp
accessible/html/HTMLElementAccessibles.cpp
accessible/html/HTMLElementAccessibles.h
accessible/tests/mochitest/elm/test_HTMLSpec.html
accessible/tests/mochitest/jsat/test_landmarks.html
accessible/tests/mochitest/role/test_general.html
--- a/accessible/base/MarkupMap.h
+++ b/accessible/base/MarkupMap.h
@@ -59,22 +59,22 @@ MARKUPMAP(figure,
           roles::FIGURE,
           Attr(xmlroles, figure))
 
 MARKUPMAP(form,
           New_HyperText,
           roles::FORM)
 
 MARKUPMAP(footer,
-          New_HyperText,
-          roles::FOOTER)
+          New_HTMLHeaderOrFooter,
+          0)
 
 MARKUPMAP(header,
-          New_HyperText,
-          roles::HEADER)
+          New_HTMLHeaderOrFooter,
+          0)
 
 MARKUPMAP(h1,
           New_HyperText,
           roles::HEADING)
 
 MARKUPMAP(h2,
           New_HyperText,
           roles::HEADING)
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -156,16 +156,19 @@ static Accessible* New_HyperText(nsICont
   { return new HyperTextAccessibleWrap(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
   { return new HTMLFigureAccessible(aContent, aContext->Document()); }
 
+static Accessible* New_HTMLHeaderOrFooter(nsIContent* aContent, Accessible* aContext)
+  { return new HTMLHeaderOrFooterAccessible(aContent, aContext->Document()); }
+
 static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
   { return new HTMLLegendAccessible(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
   { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); }
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1137,41 +1137,16 @@ HyperTextAccessible::LandmarkRole() cons
     return nullptr;
 
   // For the html landmark elements we expose them like we do ARIA landmarks to
   // make AT navigation schemes "just work".
   if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
     return nsGkAtoms::navigation;
   }
 
-  if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
-                                    nsGkAtoms::footer)) {
-    // Only map header and footer if they are not descendants of an article
-    // or section tag.
-    nsIContent* parent = mContent->GetParent();
-    while (parent) {
-      if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) {
-        break;
-      }
-      parent = parent->GetParent();
-    }
-
-    // No article or section elements found.
-    if (!parent) {
-      if (mContent->IsHTMLElement(nsGkAtoms::header)) {
-        return nsGkAtoms::banner;
-      }
-
-      if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
-        return nsGkAtoms::contentinfo;
-      }
-    }
-    return nullptr;
-  }
-
   if (mContent->IsHTMLElement(nsGkAtoms::aside)) {
     return nsGkAtoms::complementary;
   }
 
   if (mContent->IsHTMLElement(nsGkAtoms::main)) {
     return nsGkAtoms::main;
   }
 
--- a/accessible/html/HTMLElementAccessibles.cpp
+++ b/accessible/html/HTMLElementAccessibles.cpp
@@ -197,8 +197,64 @@ HTMLSummaryAccessible::NativeState()
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLSummaryAccessible: Widgets
 
 bool
 HTMLSummaryAccessible::IsWidget() const
 {
   return true;
 }
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLHeaderOrFooterAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLHeaderOrFooterAccessible, HyperTextAccessible)
+
+role
+HTMLHeaderOrFooterAccessible::NativeRole()
+{
+  // Only map header and footer if they are direct descendants of the body tag.
+  // If other sectioning or sectioning root elements, they become sections.
+  nsIContent* parent = mContent->GetParent();
+  while (parent) {
+    if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::aside,
+                             nsGkAtoms::nav, nsGkAtoms::section,
+                             nsGkAtoms::blockquote, nsGkAtoms::details,
+                             nsGkAtoms::dialog, nsGkAtoms::fieldset,
+                             nsGkAtoms::figure, nsGkAtoms::td)) {
+      break;
+    }
+    parent = parent->GetParent();
+  }
+
+  // No sectioning or sectioning root elements found.
+  if (!parent) {
+    if (mContent->IsHTMLElement(nsGkAtoms::header)) {
+      return roles::HEADER;
+    }
+
+    if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
+      return roles::FOOTER;
+    }
+  }
+
+  return roles::SECTION;
+}
+
+nsIAtom*
+HTMLHeaderOrFooterAccessible::LandmarkRole() const
+{
+  if (!HasOwnContent())
+    return nullptr;
+
+  a11y::role r = const_cast<HTMLHeaderOrFooterAccessible*>(this)->Role();
+  if (r == roles::HEADER) {
+    return nsGkAtoms::banner;
+  }
+
+  if (r == roles::FOOTER) {
+    return nsGkAtoms::contentinfo;
+  }
+
+  return nullptr;
+}
--- a/accessible/html/HTMLElementAccessibles.h
+++ b/accessible/html/HTMLElementAccessibles.h
@@ -109,12 +109,32 @@ public:
   virtual uint8_t ActionCount() override;
   virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
   virtual bool DoAction(uint8_t aIndex) override;
 
   // Widgets
   virtual bool IsWidget() const override;
 };
 
+/**
+ * Used for HTML header and footer elements.
+ */
+class HTMLHeaderOrFooterAccessible : public HyperTextAccessibleWrap
+{
+public:
+
+  HTMLHeaderOrFooterAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+    HyperTextAccessibleWrap(aContent, aDoc) {}
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // Accessible
+  virtual nsIAtom* LandmarkRole() const override;
+  virtual a11y::role NativeRole() override;
+
+protected:
+  virtual ~HTMLHeaderOrFooterAccessible() {}
+};
+
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -519,22 +519,30 @@
       obj = {
         role: ROLE_FOOTER,
         attributes: { "xml-roles": "contentinfo" },
         interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
       };
       testElm("footer", obj);
 
       obj = {
-        role: ROLE_FOOTER,
+        role: ROLE_SECTION,
         absentAttributes: { "xml-roles": "contentinfo" },
         interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
       };
       testElm("footer_in_article", obj);
+      testElm("footer_in_aside", obj);
+      testElm("footer_in_nav", obj);
       testElm("footer_in_section", obj);
+      testElm("footer_in_blockquote", obj);
+      testElm("footer_in_details", obj);
+      testElm("footer_in_dialog", obj);
+      testElm("footer_in_fieldset", obj);
+      testElm("footer_in_figure", obj);
+      testElm("footer_in_td", obj);
 
       // ////////////////////////////////////////////////////////////////////////
       // HTML:form
 
       obj = {
         role: ROLE_FORM
       };
       testElm("form", obj);
@@ -604,22 +612,30 @@
       obj = {
         role: ROLE_HEADER,
         attributes: { "xml-roles": "banner" },
         interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
       };
       testElm("header", obj);
 
       obj = {
-        role: ROLE_HEADER,
+        role: ROLE_SECTION,
         absentAttributes: { "xml-roles": "banner" },
         interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
       };
       testElm("header_in_article", obj);
+      testElm("header_in_aside", obj);
+      testElm("header_in_nav", obj);
       testElm("header_in_section", obj);
+      testElm("header_in_blockquote", obj);
+      testElm("header_in_details", obj);
+      testElm("header_in_dialog", obj);
+      testElm("header_in_fieldset", obj);
+      testElm("header_in_figure", obj);
+      testElm("header_in_td", obj);
 
       // ////////////////////////////////////////////////////////////////////////
       // HTML:hr
 
       obj = {
         role: ROLE_SEPARATOR,
       };
       testElm("hr", obj);
@@ -1466,19 +1482,43 @@
     <img src="../moz.png" alt="An awesome picture">
     <figcaption id="figcaption">Caption for the awesome picture</figcaption>
   </figure>
 
   <footer id="footer">Some copyright info</footer>
   <article>
     <footer id="footer_in_article">Some copyright info</footer>
   </article>
+  <aside>
+    <footer id="footer_in_aside">Some copyright info</footer>
+  </aside>
+  <nav>
+    <footer id="footer_in_nav">Some copyright info</footer>
+  </nav>
   <section>
     <footer id="footer_in_section">Some copyright info</footer>
   </section>
+  <blockquote>
+    <footer id="footer_in_blockquote">Some copyright info</footer>
+  </blockquote>
+  <details open="true">
+    <footer id="footer_in_details">Some copyright info</footer>
+  </details>
+  <dialog open="true">
+    <footer id="footer_in_dialog">Some copyright info</footer>
+  </dialog>
+  <fieldset>
+    <footer id="footer_in_fieldset">Some copyright info</footer>
+  </fieldset>
+  <figure>
+    <footer id="footer_in_figure">Some copyright info</footer>
+  </figure>
+  <table><tr><td>
+    <footer id="footer_in_td">Some copyright info</footer>
+  </td></tr></table>
 
   <form id="form"></form>
 
   <iframe id="frameset_container"
           src="data:text/html,<html><frameset><frame src='data:text/html,hi'></frame></frameset></html>">
   </iframe>
 
   <h1 id="h1">heading1</h1>
@@ -1487,19 +1527,43 @@
   <h4 id="h4">heading4</h4>
   <h5 id="h5">heading5</h5>
   <h6 id="h6">heading6</h6>
 
   <header id="header">A logo</header>
   <article>
     <header id="header_in_article">Not logo</header>
   </article>
+  <aside>
+    <header id="header_in_aside">Not logo</header>
+  </aside>
+  <nav>
+    <header id="header_in_nav">Not logo</header>
+  </nav>
   <section>
     <header id="header_in_section">Not logo</header>
   </section>
+  <blockquote>
+    <header id="header_in_blockquote">Not logo</header>
+  </blockquote>
+  <details open="true">
+    <header id="header_in_details">Not logo</header>
+  </details>
+  <dialog open="true">
+    <header id="header_in_dialog">Not logo</header>
+  </dialog>
+  <fieldset>
+    <header id="header_in_fieldset">Not logo</header>
+  </fieldset>
+  <figure>
+    <header id="header_in_figure">Not logo</header>
+  </figure>
+  <table><tr><td>
+    <header id="header_in_td">Not logo</header>
+  </td></tr></table>
 
   <hr id="hr">
   <p id="i_container">normal<i>italic</i></p>
   <img id="img" src="../moz.png">
 
   <input id="input_button" type="button" value="Button">
   <input id="input_checkbox" type="checkbox">
   <input id="input_checkbox_true" type="checkbox" checked>
--- a/accessible/tests/mochitest/jsat/test_landmarks.html
+++ b/accessible/tests/mochitest/jsat/test_landmarks.html
@@ -42,44 +42,44 @@
           [{"string": "contentinfo"}, {"string": "footer"}, "a footer"],
           ["a footer", {"string": "footer"}, {"string": "contentinfo"}]],
         expectedBraille: [
           [{"string": "contentinfo"}, {"string": "footerAbbr"}, "a footer"],
           ["a footer", {"string": "footerAbbr"}, {"string": "contentinfo"}]]
       }, {
         accOrElmOrID: "article_header",
         expectedUtterance: [
-          [{"string": "header"}, "a header within an article"],
-          ["a header within an article", {"string": "header"}]],
+          ["a header within an article"],
+          ["a header within an article"]],
         expectedBraille: [
-          [{"string": "headerAbbr"}, "a header within an article"],
-          ["a header within an article", {"string": "headerAbbr"}]],
+          ["a header within an article"],
+          ["a header within an article"]],
       }, {
         accOrElmOrID: "article_footer",
         expectedUtterance: [
-          [{"string": "footer"}, "a footer within an article"],
-          ["a footer within an article", {"string": "footer"}]],
+          ["a footer within an article"],
+          ["a footer within an article"]],
         expectedBraille: [
-          [{"string": "footerAbbr"}, "a footer within an article"],
-          ["a footer within an article", {"string": "footerAbbr"}]]
+          ["a footer within an article"],
+          ["a footer within an article"]]
       }, {
         accOrElmOrID: "section_header",
-        expectedUtterance: [[{"string": "header"}, "a header within a section"],
-                            ["a header within a section", {"string": "header"}]],
+        expectedUtterance: [["a header within a section"],
+                            ["a header within a section"]],
         expectedBraille: [
-          [{"string": "headerAbbr"}, "a header within a section"],
-          ["a header within a section", {"string": "headerAbbr"}]]
+          ["a header within a section"],
+          ["a header within a section"]]
       }, {
         accOrElmOrID: "section_footer",
         expectedUtterance: [
-          [{"string": "footer"}, "a footer within a section"],
-          ["a footer within a section", {"string": "footer"}]],
+          ["a footer within a section"],
+          ["a footer within a section"]],
         expectedBraille: [
-          [{"string": "footerAbbr"}, "a footer within a section"],
-          ["a footer within a section", {"string": "footerAbbr"}]]
+          ["a footer within a section"],
+          ["a footer within a section"]]
       }, {
         accOrElmOrID: "aside",
         expectedUtterance: [
           [{"string": "complementary"}, "by the way I am an aside"],
           ["by the way I am an aside", {"string": "complementary"}]],
         expectedBraille: [
           [{"string": "complementary"}, "by the way I am an aside"],
           ["by the way I am an aside", {"string": "complementary"}]]
--- a/accessible/tests/mochitest/role/test_general.html
+++ b/accessible/tests/mochitest/role/test_general.html
@@ -27,19 +27,19 @@
       testRole("article", ROLE_ARTICLE);
       testRole("aside", ROLE_NOTE);
       testRole("section", ROLE_SECTION);
 
       // Bug 996821
       // Check that landmark elements get accessibles with styled overflow.
       testRole("section_overflow", ROLE_SECTION);
       testRole("nav_overflow", ROLE_SECTION);
-      testRole("header_overflow", ROLE_HEADER);
+      testRole("header_overflow", ROLE_SECTION);
       testRole("aside_overflow", ROLE_NOTE);
-      testRole("footer_overflow", ROLE_FOOTER);
+      testRole("footer_overflow", ROLE_SECTION);
       testRole("article_overflow", ROLE_ARTICLE);
 
       // test html:div
       testRole("sec", ROLE_SECTION);
 
       // Test html:blockquote
       testRole("quote", ROLE_SECTION);