1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "ARIAMap.h"
8 #include "nsAccUtils.h"
9 #include "States.h"
10
11 #include "mozilla/dom/Element.h"
12
13 using namespace mozilla;
14 using namespace mozilla::a11y;
15 using namespace mozilla::a11y::aria;
16
17 /**
18 * Used to store state map rule data for ARIA attribute of enum type.
19 */
20 struct EnumTypeData {
21 // ARIA attribute name.
22 nsStaticAtom* const mAttrName;
23
24 // States if the attribute value is matched to the enum value. Used as
25 // Element::AttrValuesArray, last item must be nullptr.
26 nsStaticAtom* const mValues[4];
27
28 // States applied if corresponding enum values are matched.
29 const uint64_t mStates[3];
30
31 // States to clear in case of match.
32 const uint64_t mClearState;
33 };
34
35 enum ETokenType {
36 eBoolType = 0,
37 eMixedType = 1, // can take 'mixed' value
38 eDefinedIfAbsent = 2 // permanent and false state are applied if absent
39 };
40
41 /**
42 * Used to store state map rule data for ARIA attribute of token type (including
43 * mixed value).
44 */
45 struct TokenTypeData {
TokenTypeDataTokenTypeData46 TokenTypeData(nsAtom* aAttrName, uint32_t aType, uint64_t aPermanentState,
47 uint64_t aTrueState, uint64_t aFalseState = 0)
48 : mAttrName(aAttrName),
49 mType(aType),
50 mPermanentState(aPermanentState),
51 mTrueState(aTrueState),
52 mFalseState(aFalseState) {}
53
54 // ARIA attribute name.
55 nsAtom* const mAttrName;
56
57 // Type.
58 const uint32_t mType;
59
60 // State applied if the attribute is defined or mType doesn't have
61 // eDefinedIfAbsent flag set.
62 const uint64_t mPermanentState;
63
64 // States applied if the attribute value is true/false.
65 const uint64_t mTrueState;
66 const uint64_t mFalseState;
67 };
68
69 /**
70 * Map enum type attribute value to accessible state.
71 */
72 static void MapEnumType(dom::Element* aElement, uint64_t* aState,
73 const EnumTypeData& aData);
74
75 /**
76 * Map token type attribute value to states.
77 */
78 static void MapTokenType(dom::Element* aContent, uint64_t* aState,
79 const TokenTypeData& aData);
80
MapToState(EStateRule aRule,dom::Element * aElement,uint64_t * aState)81 bool aria::MapToState(EStateRule aRule, dom::Element* aElement,
82 uint64_t* aState) {
83 switch (aRule) {
84 case eARIAAutoComplete: {
85 static const EnumTypeData data = {
86 nsGkAtoms::aria_autocomplete,
87 {nsGkAtoms::inlinevalue, nsGkAtoms::list_, nsGkAtoms::both, nullptr},
88 {states::SUPPORTS_AUTOCOMPLETION,
89 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
90 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION},
91 0};
92
93 MapEnumType(aElement, aState, data);
94 return true;
95 }
96
97 case eARIABusy: {
98 static const EnumTypeData data = {
99 nsGkAtoms::aria_busy,
100 {nsGkAtoms::_true, nsGkAtoms::error, nullptr},
101 {states::BUSY, states::INVALID},
102 0};
103
104 MapEnumType(aElement, aState, data);
105 return true;
106 }
107
108 case eARIACheckableBool: {
109 static const TokenTypeData data(nsGkAtoms::aria_checked,
110 eBoolType | eDefinedIfAbsent,
111 states::CHECKABLE, states::CHECKED);
112
113 MapTokenType(aElement, aState, data);
114 return true;
115 }
116
117 case eARIACheckableMixed: {
118 static const TokenTypeData data(nsGkAtoms::aria_checked,
119 eMixedType | eDefinedIfAbsent,
120 states::CHECKABLE, states::CHECKED);
121
122 MapTokenType(aElement, aState, data);
123 return true;
124 }
125
126 case eARIACheckedMixed: {
127 static const TokenTypeData data(nsGkAtoms::aria_checked, eMixedType,
128 states::CHECKABLE, states::CHECKED);
129
130 MapTokenType(aElement, aState, data);
131 return true;
132 }
133
134 case eARIACurrent: {
135 static const TokenTypeData data(nsGkAtoms::aria_current, eBoolType, 0,
136 states::CURRENT);
137
138 MapTokenType(aElement, aState, data);
139 return true;
140 }
141
142 case eARIADisabled: {
143 static const TokenTypeData data(nsGkAtoms::aria_disabled, eBoolType, 0,
144 states::UNAVAILABLE);
145
146 MapTokenType(aElement, aState, data);
147 return true;
148 }
149
150 case eARIAExpanded: {
151 static const TokenTypeData data(nsGkAtoms::aria_expanded, eBoolType, 0,
152 states::EXPANDED, states::COLLAPSED);
153
154 MapTokenType(aElement, aState, data);
155 return true;
156 }
157
158 case eARIAHasPopup: {
159 static const TokenTypeData data(nsGkAtoms::aria_haspopup, eBoolType, 0,
160 states::HASPOPUP);
161
162 MapTokenType(aElement, aState, data);
163 return true;
164 }
165
166 case eARIAInvalid: {
167 static const TokenTypeData data(nsGkAtoms::aria_invalid, eBoolType, 0,
168 states::INVALID);
169
170 MapTokenType(aElement, aState, data);
171 return true;
172 }
173
174 case eARIAModal: {
175 static const TokenTypeData data(nsGkAtoms::aria_modal, eBoolType, 0,
176 states::MODAL);
177
178 MapTokenType(aElement, aState, data);
179 return true;
180 }
181
182 case eARIAMultiline: {
183 static const TokenTypeData data(nsGkAtoms::aria_multiline,
184 eBoolType | eDefinedIfAbsent, 0,
185 states::MULTI_LINE, states::SINGLE_LINE);
186
187 MapTokenType(aElement, aState, data);
188 return true;
189 }
190
191 case eARIAMultiSelectable: {
192 static const TokenTypeData data(
193 nsGkAtoms::aria_multiselectable, eBoolType, 0,
194 states::MULTISELECTABLE | states::EXTSELECTABLE);
195
196 MapTokenType(aElement, aState, data);
197 return true;
198 }
199
200 case eARIAOrientation: {
201 static const EnumTypeData data = {
202 nsGkAtoms::aria_orientation,
203 {nsGkAtoms::horizontal, nsGkAtoms::vertical, nullptr},
204 {states::HORIZONTAL, states::VERTICAL},
205 states::HORIZONTAL | states::VERTICAL};
206
207 MapEnumType(aElement, aState, data);
208 return true;
209 }
210
211 case eARIAPressed: {
212 static const TokenTypeData data(nsGkAtoms::aria_pressed, eMixedType, 0,
213 states::PRESSED);
214
215 MapTokenType(aElement, aState, data);
216 return true;
217 }
218
219 case eARIAReadonly: {
220 static const TokenTypeData data(nsGkAtoms::aria_readonly, eBoolType, 0,
221 states::READONLY);
222
223 MapTokenType(aElement, aState, data);
224 return true;
225 }
226
227 case eARIAReadonlyOrEditable: {
228 static const TokenTypeData data(nsGkAtoms::aria_readonly,
229 eBoolType | eDefinedIfAbsent, 0,
230 states::READONLY, states::EDITABLE);
231
232 MapTokenType(aElement, aState, data);
233 return true;
234 }
235
236 case eARIARequired: {
237 static const TokenTypeData data(nsGkAtoms::aria_required, eBoolType, 0,
238 states::REQUIRED);
239
240 MapTokenType(aElement, aState, data);
241 return true;
242 }
243
244 case eARIASelectable: {
245 static const TokenTypeData data(nsGkAtoms::aria_selected,
246 eBoolType | eDefinedIfAbsent,
247 states::SELECTABLE, states::SELECTED);
248
249 MapTokenType(aElement, aState, data);
250 return true;
251 }
252
253 case eARIASelectableIfDefined: {
254 static const TokenTypeData data(nsGkAtoms::aria_selected, eBoolType,
255 states::SELECTABLE, states::SELECTED);
256
257 MapTokenType(aElement, aState, data);
258 return true;
259 }
260
261 case eReadonlyUntilEditable: {
262 if (!(*aState & states::EDITABLE)) *aState |= states::READONLY;
263
264 return true;
265 }
266
267 case eIndeterminateIfNoValue: {
268 if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
269 !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext)) {
270 *aState |= states::MIXED;
271 }
272
273 return true;
274 }
275
276 case eFocusableUntilDisabled: {
277 if (!nsAccUtils::HasDefinedARIAToken(aElement,
278 nsGkAtoms::aria_disabled) ||
279 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
280 nsGkAtoms::_false, eCaseMatters)) {
281 *aState |= states::FOCUSABLE;
282 }
283
284 return true;
285 }
286
287 default:
288 return false;
289 }
290 }
291
MapEnumType(dom::Element * aElement,uint64_t * aState,const EnumTypeData & aData)292 static void MapEnumType(dom::Element* aElement, uint64_t* aState,
293 const EnumTypeData& aData) {
294 switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
295 aData.mValues, eCaseMatters)) {
296 case 0:
297 *aState = (*aState & ~aData.mClearState) | aData.mStates[0];
298 return;
299 case 1:
300 *aState = (*aState & ~aData.mClearState) | aData.mStates[1];
301 return;
302 case 2:
303 *aState = (*aState & ~aData.mClearState) | aData.mStates[2];
304 return;
305 }
306 }
307
MapTokenType(dom::Element * aElement,uint64_t * aState,const TokenTypeData & aData)308 static void MapTokenType(dom::Element* aElement, uint64_t* aState,
309 const TokenTypeData& aData) {
310 if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
311 if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
312 nsGkAtoms::mixed, eCaseMatters)) {
313 if (aData.mType & eMixedType) {
314 *aState |= aData.mPermanentState | states::MIXED;
315 } else { // unsupported use of 'mixed' is an authoring error
316 *aState |= aData.mPermanentState | aData.mFalseState;
317 }
318 return;
319 }
320
321 if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
322 nsGkAtoms::_false, eCaseMatters)) {
323 *aState |= aData.mPermanentState | aData.mFalseState;
324 return;
325 }
326
327 *aState |= aData.mPermanentState | aData.mTrueState;
328 return;
329 }
330
331 if (aData.mType & eDefinedIfAbsent) {
332 *aState |= aData.mPermanentState | aData.mFalseState;
333 }
334 }
335