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* 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,
88 nullptr},
89 {states::SUPPORTS_AUTOCOMPLETION,
90 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
91 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION},
92 0};
93
94 MapEnumType(aElement, aState, data);
95 return true;
96 }
97
98 case eARIABusy: {
99 static const EnumTypeData data = {
100 nsGkAtoms::aria_busy,
101 {&nsGkAtoms::_true, &nsGkAtoms::error, nullptr},
102 {states::BUSY, states::INVALID},
103 0};
104
105 MapEnumType(aElement, aState, data);
106 return true;
107 }
108
109 case eARIACheckableBool: {
110 static const TokenTypeData data(nsGkAtoms::aria_checked,
111 eBoolType | eDefinedIfAbsent,
112 states::CHECKABLE, states::CHECKED);
113
114 MapTokenType(aElement, aState, data);
115 return true;
116 }
117
118 case eARIACheckableMixed: {
119 static const TokenTypeData data(nsGkAtoms::aria_checked,
120 eMixedType | eDefinedIfAbsent,
121 states::CHECKABLE, states::CHECKED);
122
123 MapTokenType(aElement, aState, data);
124 return true;
125 }
126
127 case eARIACheckedMixed: {
128 static const TokenTypeData data(nsGkAtoms::aria_checked, eMixedType,
129 states::CHECKABLE, states::CHECKED);
130
131 MapTokenType(aElement, aState, data);
132 return true;
133 }
134
135 case eARIACurrent: {
136 static const TokenTypeData data(nsGkAtoms::aria_current, eBoolType, 0,
137 states::CURRENT);
138
139 MapTokenType(aElement, aState, data);
140 return true;
141 }
142
143 case eARIADisabled: {
144 static const TokenTypeData data(nsGkAtoms::aria_disabled, eBoolType, 0,
145 states::UNAVAILABLE);
146
147 MapTokenType(aElement, aState, data);
148 return true;
149 }
150
151 case eARIAExpanded: {
152 static const TokenTypeData data(nsGkAtoms::aria_expanded, eBoolType, 0,
153 states::EXPANDED, states::COLLAPSED);
154
155 MapTokenType(aElement, aState, data);
156 return true;
157 }
158
159 case eARIAHasPopup: {
160 static const TokenTypeData data(nsGkAtoms::aria_haspopup, eBoolType, 0,
161 states::HASPOPUP);
162
163 MapTokenType(aElement, aState, data);
164 return true;
165 }
166
167 case eARIAInvalid: {
168 static const TokenTypeData data(nsGkAtoms::aria_invalid, eBoolType, 0,
169 states::INVALID);
170
171 MapTokenType(aElement, aState, data);
172 return true;
173 }
174
175 case eARIAModal: {
176 static const TokenTypeData data(nsGkAtoms::aria_modal, eBoolType, 0,
177 states::MODAL);
178
179 MapTokenType(aElement, aState, data);
180 return true;
181 }
182
183 case eARIAMultiline: {
184 static const TokenTypeData data(nsGkAtoms::aria_multiline,
185 eBoolType | eDefinedIfAbsent, 0,
186 states::MULTI_LINE, states::SINGLE_LINE);
187
188 MapTokenType(aElement, aState, data);
189 return true;
190 }
191
192 case eARIAMultiSelectable: {
193 static const TokenTypeData data(
194 nsGkAtoms::aria_multiselectable, eBoolType, 0,
195 states::MULTISELECTABLE | states::EXTSELECTABLE);
196
197 MapTokenType(aElement, aState, data);
198 return true;
199 }
200
201 case eARIAOrientation: {
202 static const EnumTypeData data = {
203 nsGkAtoms::aria_orientation,
204 {&nsGkAtoms::horizontal, &nsGkAtoms::vertical, nullptr},
205 {states::HORIZONTAL, states::VERTICAL},
206 states::HORIZONTAL | states::VERTICAL};
207
208 MapEnumType(aElement, aState, data);
209 return true;
210 }
211
212 case eARIAPressed: {
213 static const TokenTypeData data(nsGkAtoms::aria_pressed, eMixedType, 0,
214 states::PRESSED);
215
216 MapTokenType(aElement, aState, data);
217 return true;
218 }
219
220 case eARIAReadonly: {
221 static const TokenTypeData data(nsGkAtoms::aria_readonly, eBoolType, 0,
222 states::READONLY);
223
224 MapTokenType(aElement, aState, data);
225 return true;
226 }
227
228 case eARIAReadonlyOrEditable: {
229 static const TokenTypeData data(nsGkAtoms::aria_readonly,
230 eBoolType | eDefinedIfAbsent, 0,
231 states::READONLY, states::EDITABLE);
232
233 MapTokenType(aElement, aState, data);
234 return true;
235 }
236
237 case eARIAReadonlyOrEditableIfDefined: {
238 static const TokenTypeData data(nsGkAtoms::aria_readonly, eBoolType, 0,
239 states::READONLY, states::EDITABLE);
240
241 MapTokenType(aElement, aState, data);
242 return true;
243 }
244
245 case eARIARequired: {
246 static const TokenTypeData data(nsGkAtoms::aria_required, eBoolType, 0,
247 states::REQUIRED);
248
249 MapTokenType(aElement, aState, data);
250 return true;
251 }
252
253 case eARIASelectable: {
254 static const TokenTypeData data(nsGkAtoms::aria_selected,
255 eBoolType | eDefinedIfAbsent,
256 states::SELECTABLE, states::SELECTED);
257
258 MapTokenType(aElement, aState, data);
259 return true;
260 }
261
262 case eARIASelectableIfDefined: {
263 static const TokenTypeData data(nsGkAtoms::aria_selected, eBoolType,
264 states::SELECTABLE, states::SELECTED);
265
266 MapTokenType(aElement, aState, data);
267 return true;
268 }
269
270 case eReadonlyUntilEditable: {
271 if (!(*aState & states::EDITABLE)) *aState |= states::READONLY;
272
273 return true;
274 }
275
276 case eIndeterminateIfNoValue: {
277 if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
278 !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext))
279 *aState |= states::MIXED;
280
281 return true;
282 }
283
284 case eFocusableUntilDisabled: {
285 if (!nsAccUtils::HasDefinedARIAToken(aElement,
286 nsGkAtoms::aria_disabled) ||
287 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
288 nsGkAtoms::_false, eCaseMatters))
289 *aState |= states::FOCUSABLE;
290
291 return true;
292 }
293
294 default:
295 return false;
296 }
297 }
298
MapEnumType(dom::Element * aElement,uint64_t * aState,const EnumTypeData & aData)299 static void MapEnumType(dom::Element* aElement, uint64_t* aState,
300 const EnumTypeData& aData) {
301 switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
302 aData.mValues, eCaseMatters)) {
303 case 0:
304 *aState = (*aState & ~aData.mClearState) | aData.mStates[0];
305 return;
306 case 1:
307 *aState = (*aState & ~aData.mClearState) | aData.mStates[1];
308 return;
309 case 2:
310 *aState = (*aState & ~aData.mClearState) | aData.mStates[2];
311 return;
312 }
313 }
314
MapTokenType(dom::Element * aElement,uint64_t * aState,const TokenTypeData & aData)315 static void MapTokenType(dom::Element* aElement, uint64_t* aState,
316 const TokenTypeData& aData) {
317 if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
318 if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
319 nsGkAtoms::mixed, eCaseMatters)) {
320 if (aData.mType & eMixedType)
321 *aState |= aData.mPermanentState | states::MIXED;
322 else // unsupported use of 'mixed' is an authoring error
323 *aState |= aData.mPermanentState | aData.mFalseState;
324 return;
325 }
326
327 if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
328 nsGkAtoms::_false, eCaseMatters)) {
329 *aState |= aData.mPermanentState | aData.mFalseState;
330 return;
331 }
332
333 *aState |= aData.mPermanentState | aData.mTrueState;
334 return;
335 }
336
337 if (aData.mType & eDefinedIfAbsent)
338 *aState |= aData.mPermanentState | aData.mFalseState;
339 }
340