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
authorMarco Zehe <mzehe@mozilla.com>
Mon, 28 Aug 2017 14:30:17 +0200
changeset 377737 03245d48dbed8a49319cacdbe586d22b0aa4dee2
parent 377736 b0cc5205f6ed2d92ed4cbc7b6c75de2538cb6309
child 377738 47914cdf92a5265664e1b83be82faa5d5b966d55
push id32415
push userkwierso@gmail.com
push dateThu, 31 Aug 2017 02:52:55 +0000
treeherdermozilla-central@04b6be50a252 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerseeejay
bugs1316285
milestone57.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 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);