Bug 1576509: Add a function to announce a message to screen reader users. r=Gijs,yzen
authorJames Teh <jteh@mozilla.com>
Wed, 02 Oct 2019 05:53:52 +0000
changeset 495948 e062c3bc7cbd57ec80757c4ad04de799660577a1
parent 495947 daad734d5665c464adf28e68251a63fbc556f39a
child 495949 daee59fa527009d0bdc0e34499310018c8da0bff
push id114140
push userdvarga@mozilla.com
push dateWed, 02 Oct 2019 18:04:51 +0000
treeherdermozilla-inbound@32eb0ea893f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, yzen
bugs1576509
milestone71.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 1576509: Add a function to announce a message to screen reader users. r=Gijs,yzen Differential Revision: https://phabricator.services.mozilla.com/D47717
accessible/tests/browser/events/browser.ini
accessible/tests/browser/events/browser_test_A11yUtils_announce.js
accessible/tests/mochitest/promisified-events.js
browser/base/content/browser-a11yUtils.js
browser/base/content/browser.js
browser/base/content/browser.xhtml
browser/base/jar.mn
--- a/accessible/tests/browser/events/browser.ini
+++ b/accessible/tests/browser/events/browser.ini
@@ -6,8 +6,9 @@ support-files =
 
 [browser_test_docload.js]
 skip-if = e10s
 [browser_test_scrolling.js]
 [browser_test_textcaret.js]
 [browser_test_focus_browserui.js]
 [browser_test_focus_dialog.js]
 [browser_test_focus_urlbar.js]
+[browser_test_A11yUtils_announce.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_A11yUtils_announce.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
+
+// Check that the browser A11yUtils.announce() function works correctly.
+// Note that this does not use mozilla::a11y::Accessible::Announce and a11y
+// announcement events, as these aren't yet supported on desktop.
+async function runTests() {
+  const alert = document.getElementById("a11y-announcement");
+  let alerted = waitForEvent(EVENT_ALERT, alert);
+  A11yUtils.announce("first");
+  let event = await alerted;
+  const alertAcc = event.accessible;
+  is(alertAcc.role, ROLE_ALERT);
+  ok(!alertAcc.name);
+  is(alertAcc.childCount, 1);
+  is(alertAcc.firstChild.name, "first");
+
+  alerted = waitForEvent(EVENT_ALERT, alertAcc);
+  A11yUtils.announce("second");
+  event = await alerted;
+  ok(!alertAcc.name);
+  is(alertAcc.childCount, 1);
+  is(alertAcc.firstChild.name, "second");
+}
+
+addAccessibleTask(``, runTests);
--- a/accessible/tests/mochitest/promisified-events.js
+++ b/accessible/tests/mochitest/promisified-events.js
@@ -9,17 +9,17 @@
 /* import-globals-from common.js */
 
 /* exported EVENT_ANNOUNCEMENT, EVENT_REORDER, EVENT_SCROLLING,
             EVENT_SCROLLING_END, EVENT_SHOW, EVENT_TEXT_INSERTED,
             EVENT_TEXT_REMOVED, EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE,
             EVENT_TEXT_ATTRIBUTE_CHANGED, EVENT_TEXT_CARET_MOVED,
             EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
             EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
-            EVENT_DOCUMENT_RELOAD, EVENT_VIRTUALCURSOR_CHANGED,
+            EVENT_DOCUMENT_RELOAD, EVENT_VIRTUALCURSOR_CHANGED, EVENT_ALERT,
             UnexpectedEvents, waitForEvent, waitForEvents, waitForOrderedEvents */
 
 const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT;
 const EVENT_DOCUMENT_LOAD_COMPLETE =
   nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SCROLLING = nsIAccessibleEvent.EVENT_SCROLLING;
@@ -34,16 +34,17 @@ const EVENT_TEXT_REMOVED = nsIAccessible
 const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
 const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
 const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
 const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
 const EVENT_VIRTUALCURSOR_CHANGED =
   nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
+const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
 
 const EventsLogger = {
   enabled: false,
 
   log(msg) {
     if (this.enabled) {
       info(msg);
     }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-a11yUtils.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is loaded into the browser window scope.
+/* eslint-env mozilla/browser-window */
+
+/**
+ * Utility functions for UI accessibility.
+ */
+
+var A11yUtils = {
+  /**
+   * Announce a message to the user.
+   * This should only be used when something happens that is important to the
+   * user and will be noticed visually, but is not related to the focused
+   * control and is not a pop-up such as a doorhanger.
+   * For example, this could be used to indicate that Reader View is available
+   * or that Firefox is making a recommendation via the toolbar.
+   * This must be used with caution, as it can create unwanted verbosity and
+   * can thus hinder rather than help users if used incorrectly.
+   * Please only use this after consultation with the Mozilla accessibility
+   * team.
+   * @param aMessage The message to announce.
+   * @param aSource The element with which the announcement is associated.
+   *        This should generally be something the user can interact with to
+   *        respond to the announcement.
+   *        For example, for an announcement indicating that Reader View is
+   *        available, this should be the Reader View button on the toolbar.
+   */
+  announce(aMessage, aSource = document) {
+    // For now, we don't use aSource, but it might be useful in future.
+    // For example, we might use it when we support announcement events on
+    // more platforms or it could be used to have a keyboard shortcut which
+    // focuses the last element to announce a message.
+    let live = document.getElementById("a11y-announcement");
+    // We use role="alert" because JAWS doesn't support aria-live in browser
+    // chrome.
+    // Gecko a11y needs an insertion to trigger an alert event. This is why
+    // we can't just use aria-label on the alert.
+    if (live.firstChild) {
+      live.firstChild.remove();
+    }
+    let label = document.createElement("label");
+    label.setAttribute("aria-label", aMessage);
+    live.appendChild(label);
+  },
+};
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -232,16 +232,21 @@ XPCOMUtils.defineLazyScriptGetter(
   "pktUI",
   "chrome://pocket/content/main.js"
 );
 XPCOMUtils.defineLazyScriptGetter(
   this,
   "ToolbarKeyboardNavigator",
   "chrome://browser/content/browser-toolbarKeyNav.js"
 );
+XPCOMUtils.defineLazyScriptGetter(
+  this,
+  "A11yUtils",
+  "chrome://browser/content/browser-a11yUtils.js"
+);
 
 // lazy service getters
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   ContentPrefService2: [
     "@mozilla.org/content-pref/service;1",
     "nsIContentPrefService2",
   ],
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -1410,9 +1410,11 @@
 # Everything that needs to straddle the line between chrome and content, without being
 # its own widget-level window, needs to go in here, and set the renderroot="popover"
 # attribute, or it will fail to render properly in WebRender.
 #include popovers.inc
 
   <vbox id="browser-bottombox" layer="true" renderroot="content">
     <!-- gNotificationBox will be added here lazily. -->
   </vbox>
+
+  <html:div id="a11y-announcement" role="alert"/>
 </window>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -31,16 +31,17 @@ browser.jar:
         content/browser/aboutRobots-icon.png          (content/aboutRobots-icon.png)
         content/browser/aboutFrameCrashed.html        (content/aboutFrameCrashed.html)
         content/browser/aboutTabCrashed.css           (content/aboutTabCrashed.css)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
         content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xhtml                 (content/browser.xhtml)
+        content/browser/browser-a11yUtils.js          (content/browser-a11yUtils.js)
         content/browser/browser-addons.js             (content/browser-addons.js)
         content/browser/browser-allTabsMenu.js        (content/browser-allTabsMenu.js)
         content/browser/browser-captivePortal.js      (content/browser-captivePortal.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
 #ifndef MOZILLA_OFFICIAL
         content/browser/browser-development-helpers.js (content/browser-development-helpers.js)