1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
/* 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 {string} [options.id] The Fluent id of the message to announce. The
* ftl file must already be included in browser.xhtml. This must be
* specified unless a raw message is specified instead.
* @param {object} [options.args] Arguments for the Fluent message.
* @param {string} [options.raw] The raw, already localized message to
* announce. You should generally prefer a Fluent id instead, but in
* rare cases, this might not be feasible.
* @param {Element} [options.source] 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.
*/
async announce({ id = null, args = {}, raw = null, source = document } = {}) {
if ((!id && !raw) || (id && raw)) {
throw new Error("One of raw or id must be specified.");
}
// Cancel a previous pending call if any.
if (this._cancelAnnounce) {
this._cancelAnnounce();
this._cancelAnnounce = null;
}
let message;
if (id) {
let cancel = false;
this._cancelAnnounce = () => (cancel = true);
message = await document.l10n.formatValue(id, args);
if (cancel) {
// announce() was called again while we were waiting for translation.
return;
}
// No more async operations from this point.
this._cancelAnnounce = null;
} else {
// We run fully synchronously if a raw message is provided.
message = raw;
}
// For now, we don't use source, 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", message);
live.appendChild(label);
},
};
|