Bug 1589859 - Fix accessibility labels in message pane headerarea. r=mkmelin
authorAlessandro Castellani <alessandro@thunderbird.net>
Fri, 20 Mar 2020 10:53:52 -0700
changeset 38541 d51e4a31fe1d03d1ce4c068b522aed0376174e7a
parent 38540 3528b15b3c008d410a37abdb2c363fc85954a051
child 38542 efb0a54d4ae2bddb84fe2bcad4373bb655f14329
push id400
push userclokep@gmail.com
push dateMon, 04 May 2020 18:56:09 +0000
reviewersmkmelin
bugs1589859
Bug 1589859 - Fix accessibility labels in message pane headerarea. r=mkmelin
mail/base/content/mailWidgets.js
mail/base/content/msgHdrView.inc.xhtml
mail/test/browser/message-header/browser_messageHeader.js
--- a/mail/base/content/mailWidgets.js
+++ b/mail/base/content/mailWidgets.js
@@ -44,16 +44,26 @@
 
   class MozMailHeaderfield extends MozXULElement {
     connectedCallback() {
       this.setAttribute("context", "copyPopup");
       this.classList.add("headerValue");
     }
 
     set headerValue(val) {
+      // Solve the accessibility problem by manually fetching the translated
+      // string from the label and updating the attribute. Bug 1493608
+      if (this.getAttribute("aria-labelledby")) {
+        let ariaLabel = document.getElementById(
+          this.getAttribute("aria-labelledby")
+        );
+        this.setAttribute("aria-label", `${ariaLabel.value}: ${val}`);
+        this.removeAttribute("aria-labelledby");
+      }
+
       return (this.textContent = val);
     }
   }
   customElements.define("mail-headerfield", MozMailHeaderfield);
 
   class MozMailUrlfield extends MozMailHeaderfield {
     constructor() {
       super();
@@ -114,27 +124,46 @@
         const label = document.createXULElement("label");
         label.setAttribute("value", tagName);
         label.className = "tagvalue";
         label.setAttribute(
           "style",
           "color: " + textColor + "; background-color: " + color + ";"
         );
 
+        // Solve the accessibility problem by manually fetching the translated
+        // string from the label and updating the attribute. Bug 1493608
+        let ariaLabel = document.getElementById(
+          this.getAttribute("aria-labelledby")
+        );
+        label.setAttribute("aria-label", `${ariaLabel.value}: ${tagName}`);
+        label.removeAttribute("aria-labelledby");
+
         this.appendChild(label);
       }
     }
   }
   customElements.define("mail-tagfield", MozMailHeaderfieldTags);
 
   class MozMailNewsgroup extends MozXULElement {
     connectedCallback() {
       this.classList.add("emailDisplayButton");
       this.setAttribute("context", "newsgroupPopup");
       this.setAttribute("popup", "newsgroupPopup");
+
+      // Solve the accessibility problem by manually fetching the translated
+      // string from the label and updating the attribute. Bug 1493608
+      let ariaLabel = document.getElementById(
+        this.getAttribute("aria-labelledby")
+      );
+      this.setAttribute(
+        "aria-label",
+        `${ariaLabel.value}: ${this.getAttribute("newsgroup")}`
+      );
+      this.removeAttribute("aria-labelledby");
     }
   }
   customElements.define("mail-newsgroup", MozMailNewsgroup);
 
   class MozMailNewsgroupsHeaderfield extends MozXULElement {
     connectedCallback() {
       this.classList.add("headerValueBox");
       this.mNewsgroups = [];
@@ -151,16 +180,21 @@
           const textNode = document.createXULElement("label");
           textNode.setAttribute("value", ",");
           textNode.setAttribute("class", "newsgroupSeparator");
           this.appendChild(textNode);
         }
 
         newNode.textContent = this.mNewsgroups[i];
         newNode.setAttribute("newsgroup", this.mNewsgroups[i]);
+        newNode.setAttribute(
+          "aria-labelledby",
+          this.getAttribute("aria-labelledby")
+        );
+        this.removeAttribute("aria-labelledby");
         this.appendChild(newNode);
       }
     }
 
     clearHeaderValues() {
       this.mNewsgroups = [];
       while (this.hasChildNodes()) {
         this.lastChild.remove();
@@ -438,16 +472,21 @@
   class MozMailEmailheaderfield extends MozXULElement {
     connectedCallback() {
       if (this.hasChildNodes() || this.delayConnectedCallback()) {
         return;
       }
       this._mailEmailAddress = document.createXULElement("mail-emailaddress");
       this._mailEmailAddress.classList.add("headerValue");
       this._mailEmailAddress.setAttribute("containsEmail", "true");
+      this._mailEmailAddress.setAttribute(
+        "aria-labelledby",
+        this.getAttribute("aria-labelledby")
+      );
+      this._mailEmailAddress.removeAttribute("aria-labelledby");
 
       this.appendChild(this._mailEmailAddress);
     }
 
     get emailAddressNode() {
       return this._mailEmailAddress;
     }
   }
@@ -909,24 +948,24 @@
 
       this.longEmailAddresses = document.createXULElement("hbox");
       this.longEmailAddresses.classList.add("headerValueBox");
       this.longEmailAddresses.setAttribute("flex", "1");
       this.longEmailAddresses.setAttribute("singleline", "true");
       this.longEmailAddresses.setAttribute("align", "baseline");
 
       this.emailAddresses = document.createXULElement("description");
-      this.emailAddresses.classList.add("class", "headerValue");
+      this.emailAddresses.classList.add("headerValue");
       this.emailAddresses.setAttribute("containsEmail", "true");
       this.emailAddresses.setAttribute("flex", "1");
       this.emailAddresses.setAttribute("orient", "vertical");
       this.emailAddresses.setAttribute("pack", "start");
 
       this.more = document.createXULElement("label");
-      this.more.classList.add("class", "moreIndicator");
+      this.more.classList.add("moreIndicator");
       this.more.addEventListener("click", this.toggleWrap.bind(this));
       this.more.setAttribute("collapsed", "true");
 
       this.longEmailAddresses.appendChild(this.emailAddresses);
       this.appendChild(this.longEmailAddresses);
       this.appendChild(this.more);
     }
 
@@ -1003,16 +1042,30 @@
             this.commaNodeWidth = this.emailAddresses.lastElementChild.clientWidth;
           }
         }
 
         let newAddressNode = document.createXULElement("mail-emailaddress");
         // Stash the headerName somewhere that UpdateEmailNodeDetails will be
         // able to find it.
         newAddressNode.setAttribute("headerName", this.headerName);
+
+        // Solve the accessibility problem by manually fetching the translated
+        // string from the label and updating the attribute. Bug 1493608
+        let ariaLabel = document.getElementById(
+          this.getAttribute("aria-labelledby")
+        );
+        newAddressNode.setAttribute(
+          "aria-label",
+          `${ariaLabel.value}: ${this.addresses[i].fullAddress ||
+            this.addresses[i].displayName ||
+            ""}`
+        );
+        newAddressNode.removeAttribute("aria-labelledby");
+
         this._updateEmailAddressNode(newAddressNode, this.addresses[i]);
         newAddressNode = this.emailAddresses.appendChild(newAddressNode);
         addrCount++;
 
         if (all) {
           continue;
         }
 
@@ -2247,16 +2300,17 @@
       });
       for (let css of fixedClassed) {
         pill.emailInput.classList.add(css);
       }
       pill.emailInput.setAttribute(
         "aria-labelledby",
         element.getAttribute("aria-labelledby")
       );
+      element.removeAttribute("aria-labelledby");
 
       let params = JSON.parse(
         pill.emailInput.getAttribute("autocompletesearchparam")
       );
       params.type = element.getAttribute("recipienttype");
       pill.emailInput.setAttribute(
         "autocompletesearchparam",
         JSON.stringify(params)
--- a/mail/base/content/msgHdrView.inc.xhtml
+++ b/mail/base/content/msgHdrView.inc.xhtml
@@ -239,17 +239,18 @@
                                   <html:tr id="expandedfromRow">
                                     <html:th id="expandedfromTableHeader">
                                       <label id="expandedfromLabel" class="headerName"
                                              value="&fromField4.label;"
                                              valueFrom="&fromField4.label;" valueAuthor="&author.label;"
                                              control="expandedfromBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-multi-emailheaderfield id="expandedfromBox"/>
+                                      <mail-multi-emailheaderfield id="expandedfromBox"
+                                                                   aria-labelledby="expandedfromLabel"/>
                                     </html:td>
                                   </html:tr>
                                 </html:table>
                               </hbox>
 
                               <hbox id="expandedHeadersBottomBox">
 
                                 <!-- The grid that contains all headers except the first one,
@@ -260,161 +261,177 @@
                                   <html:tr id="expandedsubjectRow">
                                     <html:th id="expandedsubjectTableHeader">
                                       <label id="expandedsubjectLabel" class="headerName"
                                              value="&subjectField4.label;"
                                              control="expandedsubjectBox"/>
                                     </html:th>
                                     <html:td>
                                       <mail-headerfield id="expandedsubjectBox"
+                                                        aria-labelledby="expandedsubjectLabel"
                                                         headerName="subject" flex="1"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedorganizationRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedorganizationLabel" class="headerName"
                                              value="&organizationField4.label;"
                                              control="expandedorganizationBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-headerfield id="expandedorganizationBox"/>
+                                      <mail-headerfield id="expandedorganizationBox"
+                                                        aria-labelledby="expandedorganizationLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedsenderRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedsenderLabel" class="headerName"
                                              value="&senderField4.label;" control="expandedsenderBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-emailheaderfield id="expandedsenderBox"/>
+                                      <mail-emailheaderfield id="expandedsenderBox"
+                                                             aria-labelledby="expandedsenderLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedreply-toRow">
                                     <html:th>
                                       <label id="expandedreply-toLabel" class="headerName"
                                              value="&replyToField4.label;"
                                              control="expandedreply-toBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-multi-emailheaderfield id="expandedreply-toBox"/>
+                                      <mail-multi-emailheaderfield id="expandedreply-toBox"
+                                                                   aria-labelledby="expandedreply-toLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedtoRow">
                                     <html:th>
                                       <label id="expandedtoLabel" class="headerName"
                                              value="&toField4.label;" control="expandedtoBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-multi-emailheaderfield id="expandedtoBox"/>
+                                      <mail-multi-emailheaderfield id="expandedtoBox"
+                                                                   aria-labelledby="expandedtoLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedccRow">
                                     <html:th>
                                       <label id="expandedccLabel" class="headerName"
                                              value="&ccField4.label;" control="expandedccBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-multi-emailheaderfield id="expandedccBox"/>
+                                      <mail-multi-emailheaderfield id="expandedccBox"
+                                                                   aria-labelledby="expandedccLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedbccRow">
                                     <html:th>
                                       <label id="expandedbccLabel" class="headerName"
                                              value="&bccField4.label;" control="expandedbccBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-multi-emailheaderfield id="expandedbccBox"/>
+                                      <mail-multi-emailheaderfield id="expandedbccBox"
+                                                                   aria-labelledby="expandedbccLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandednewsgroupsRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandednewsgroupsLabel" class="headerName"
                                              value="&newsgroupsField4.label;"
                                              control="expandednewsgroupsBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-newsgroups-headerfield id="expandednewsgroupsBox"/>
+                                      <mail-newsgroups-headerfield id="expandednewsgroupsBox"
+                                                                   aria-labelledby="expandednewsgroupsLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedfollowup-toRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedfollowup-toLabel" class="headerName"
                                              value="&followupToField4.label;"
                                              control="expandedfollowup-toBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-newsgroups-headerfield id="expandedfollowup-toBox"/>
+                                      <mail-newsgroups-headerfield id="expandedfollowup-toBox"
+                                                                   aria-labelledby="expandedfollowup-toLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandeddateRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandeddateLabel" class="headerName"
                                              value="&dateField4.label;" control="expandeddateBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-headerfield id="expandeddateBox"/>
+                                      <mail-headerfield id="expandeddateBox"
+                                                        aria-labelledby="expandeddateLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedmessage-idRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedmessage-idLabel" class="headerName"
                                              value="&messageIdField4.label;"
                                              control="expandedmessage-idBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-messageids-headerfield id="expandedmessage-idBox"/>
+                                      <mail-messageids-headerfield id="expandedmessage-idBox"
+                                                                   aria-labelledby="expandedmessage-idLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedin-reply-toRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedin-reply-toLabel" class="headerName"
                                              value="&inReplyToField4.label;"
                                              control="expandedin-reply-toBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-messageids-headerfield id="expandedin-reply-toBox"/>
+                                      <mail-messageids-headerfield id="expandedin-reply-toBox"
+                                                                   aria-labelledby="expandedin-reply-toLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedreferencesRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedreferencesLabel" class="headerName"
                                              value="&referencesField4.label;"
                                              control="expandedreferencesBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-messageids-headerfield id="expandedreferencesBox"/>
+                                      <mail-messageids-headerfield id="expandedreferencesBox"
+                                                                   aria-labelledby="expandedreferencesLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedtagsRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedtagsLabel" class="headerName"
                                              value="&tagsHdr4.label;" control="expandedtagsBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-tagfield id="expandedtagsBox"/>
+                                      <mail-tagfield id="expandedtagsBox"
+                                                     aria-labelledby="expandedtagsLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandedcontent-baseRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandedcontent-baseLabel" class="headerName"
                                              value="&originalWebsite4.label;"
                                              control="expandedcontent-baseBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-urlfield id="expandedcontent-baseBox"/>
+                                      <mail-urlfield id="expandedcontent-baseBox"
+                                                     aria-labelledby="expandedcontent-baseLabel"/>
                                     </html:td>
                                   </html:tr>
                                   <html:tr id="expandeduser-agentRow" hidden="hidden">
                                     <html:th>
                                       <label id="expandeduser-agentLabel" class="headerName"
                                              value="&userAgentField4.label;"
                                              control="expandeduser-agentBox"/>
                                     </html:th>
                                     <html:td>
-                                      <mail-headerfield id="expandeduser-agentBox"/>
+                                      <mail-headerfield id="expandeduser-agentBox"
+                                                        aria-labelledby="expandeduser-agentLabel"/>
                                     </html:td>
                                   </html:tr>
                                 </html:table>
 
                                 <!-- perhaps this should be a customizable toolbar too -->
                                 <vbox id="otherActionsBox" align="end">
                                   <hbox id="dateValueBox" align="start">
                                     <hbox id="smimeBox" collapsed="true">
--- a/mail/test/browser/message-header/browser_messageHeader.js
+++ b/mail/test/browser/message-header/browser_messageHeader.js
@@ -211,160 +211,148 @@ add_task(function test_add_tag_with_real
 });
 
 /**
  * @param headerName used for pretty-printing in exceptions
  * @param headerValueElement  Function returning the DOM element
  *                            with the data.
  * @param expectedName  Function returning the expected value of
  *                      nsIAccessible.name for the DOM element in question
- * @param expectedRole the expected value for nsIAccessible.role
  */
 let headersToTest = [
   {
     headerName: "Subject",
     headerValueElement(mc) {
       return mc.e("expandedsubjectBox", { class: "headerValue" });
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedsubjectLabel").value +
         ": " +
         headerValueElement.textContent
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Content-Base",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedcontent-baseBox > .headerValue.text-link.headerValueUrl"
+        "#expandedcontent-baseBox.headerValue.text-link.headerValueUrl"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedcontent-baseLabel").value +
         ": " +
         headerValueElement.textContent
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "From",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedfromBox > mail-emailaddress.emailDisplayButton"
+        "#expandedfromBox > .headerValueBox > .headerValue > mail-emailaddress.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedfromLabel").value +
         ": " +
-        headerValueElement.parentNode.getAttribute("fullAddress")
+        headerValueElement.getAttribute("fullAddress")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "To",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedtoBox > mail-emailaddress.emailDisplayButton"
+        "#expandedtoBox > .headerValueBox > .headerValue > mail-emailaddress.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedtoLabel").value +
         ": " +
-        headerValueElement.parentNode.getAttribute("fullAddress")
+        headerValueElement.getAttribute("fullAddress")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Cc",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedccBox > mail-emailaddress.emailDisplayButton"
+        "#expandedccBox > .headerValueBox > .headerValue > mail-emailaddress.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedccLabel").value +
         ": " +
-        headerValueElement.parentNode.getAttribute("fullAddress")
+        headerValueElement.getAttribute("fullAddress")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Bcc",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedbccBox > mail-emailaddress.emailDisplayButton"
+        "#expandedbccBox > .headerValueBox > .headerValue > mail-emailaddress.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedbccLabel").value +
         ": " +
-        headerValueElement.parentNode.getAttribute("fullAddress")
+        headerValueElement.getAttribute("fullAddress")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Reply-To",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandedreply-toBox > mail-emailaddress.emailDisplayButton"
+        "#expandedreply-toBox > .headerValueBox > .headerValue > mail-emailaddress.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedreply-toLabel").value +
         ": " +
-        headerValueElement.parentNode.getAttribute("fullAddress")
+        headerValueElement.getAttribute("fullAddress")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Newsgroups",
     headerValueElement(mc) {
       return mc.window.document.querySelector(
-        "#expandednewsgroupsBox > mail-newsgroup.newsgrouplabel"
+        "#expandednewsgroupsBox > mail-newsgroup.emailDisplayButton"
       );
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandednewsgroupsLabel").value +
         ": " +
-        headerValueElement.parentNode.parentNode.getAttribute("newsgroup")
+        headerValueElement.getAttribute("newsgroup")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_ENTRY,
   },
   {
     headerName: "Tags",
     headerValueElement(mc) {
-      return mc.window.document.querySelector(
-        "#expandedtagsBox > .tagvalue.blc-FF0000"
-      );
+      return mc.window.document.querySelector("#expandedtagsBox > .tagvalue");
     },
     expectedName(mc, headerValueElement) {
       return (
         mc.e("expandedtagsLabel").value +
         ": " +
         headerValueElement.getAttribute("value")
       );
     },
-    expectedRole: Ci.nsIAccessibleRole.ROLE_LABEL,
   },
 ];
 
 // used to get the accessible object for a DOM node
 let gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
   Ci.nsIAccessibilityService
 );
 
@@ -387,23 +375,16 @@ function verify_header_a11y(aHeaderInfo)
   let headerAccessible;
   mc.waitFor(
     () =>
       (headerAccessible = gAccService.getAccessibleFor(headerValueElement)) !=
       null,
     `didn't find accessible element for header '${aHeaderInfo.headerName}'`
   );
 
-  Assert.equal(
-    headerAccessible.role,
-    aHeaderInfo.expectedRole,
-    `role for ${aHeaderInfo.headerName} was ${headerAccessible.role}; ` +
-      `should have been ${aHeaderInfo.expectedRole}`
-  );
-
   let expectedName = aHeaderInfo.expectedName(mc, headerValueElement);
   Assert.equal(
     headerAccessible.name,
     expectedName,
     `headerAccessible.name for ${aHeaderInfo.headerName} ` +
       `was '${headerAccessible.name}'; expected '${expectedName}'`
   );
 }
@@ -432,17 +413,17 @@ add_task(function test_a11y_attrs() {
 
   // select and open the interesting message
   let curMessage = select_click_row(mc.dbView.findIndexOfMsgHdr(hdr, false));
 
   // make sure it loads
   assert_selected_and_displayed(mc, curMessage);
 
   headersToTest.forEach(verify_header_a11y);
-}).skip(); // disabled for now, see bug 1489748
+});
 
 add_task(function test_more_button_with_many_recipients() {
   // Start on the interesting message.
   let curMessage = select_click_row(0);
 
   // make sure it loads
   wait_for_message_display_completion(mc);
   assert_selected_and_displayed(mc, curMessage);