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 {
22 // ARIA attribute name.
23 nsIAtom* const mAttrName;
24
25 // States if the attribute value is matched to the enum value. Used as
26 // nsIContent::AttrValuesArray, last item must be nullptr.
27 nsIAtom* const* const mValues[4];
28
29 // States applied if corresponding enum values are matched.
30 const uint64_t mStates[3];
31
32 // States to clear in case of match.
33 const uint64_t mClearState;
34 };
35
36 enum ETokenType
37 {
38 eBoolType = 0,
39 eMixedType = 1, // can take 'mixed' value
40 eDefinedIfAbsent = 2 // permanent and false state are applied if absent
41 };
42
43 /**
44 * Used to store state map rule data for ARIA attribute of token type (including
45 * mixed value).
46 */
47 struct TokenTypeData
48 {
TokenTypeDataTokenTypeData49 TokenTypeData(nsIAtom* aAttrName, uint32_t aType,
50 uint64_t aPermanentState,
51 uint64_t aTrueState,
52 uint64_t aFalseState = 0) :
53 mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState),
54 mTrueState(aTrueState), mFalseState(aFalseState)
55 { }
56
57 // ARIA attribute name.
58 nsIAtom* const mAttrName;
59
60 // Type.
61 const uint32_t mType;
62
63 // State applied if the attribute is defined or mType doesn't have
64 // eDefinedIfAbsent flag set.
65 const uint64_t mPermanentState;
66
67 // States applied if the attribute value is true/false.
68 const uint64_t mTrueState;
69 const uint64_t mFalseState;
70 };
71
72 /**
73 * Map enum type attribute value to accessible state.
74 */
75 static void MapEnumType(dom::Element* aElement, uint64_t* aState,
76 const EnumTypeData& aData);
77
78 /**
79 * Map token type attribute value to states.
80 */
81 static void MapTokenType(dom::Element* aContent, uint64_t* aState,
82 const TokenTypeData& aData);
83
84 bool
MapToState(EStateRule aRule,dom::Element * aElement,uint64_t * aState)85 aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState)
86 {
87 switch (aRule) {
88 case eARIAAutoComplete:
89 {
90 static const EnumTypeData data = {
91 nsGkAtoms::aria_autocomplete,
92 { &nsGkAtoms::inlinevalue,
93 &nsGkAtoms::list,
94 &nsGkAtoms::both, nullptr },
95 { states::SUPPORTS_AUTOCOMPLETION,
96 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
97 states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION }, 0
98 };
99
100 MapEnumType(aElement, aState, data);
101 return true;
102 }
103
104 case eARIABusy:
105 {
106 static const EnumTypeData data = {
107 nsGkAtoms::aria_busy,
108 { &nsGkAtoms::_true,
109 &nsGkAtoms::error, nullptr },
110 { states::BUSY,
111 states::INVALID }, 0
112 };
113
114 MapEnumType(aElement, aState, data);
115 return true;
116 }
117
118 case eARIACheckableBool:
119 {
120 static const TokenTypeData data(
121 nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent,
122 states::CHECKABLE, states::CHECKED);
123
124 MapTokenType(aElement, aState, data);
125 return true;
126 }
127
128 case eARIACheckableMixed:
129 {
130 static const TokenTypeData data(
131 nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent,
132 states::CHECKABLE, states::CHECKED);
133
134 MapTokenType(aElement, aState, data);
135 return true;
136 }
137
138 case eARIACheckedMixed:
139 {
140 static const TokenTypeData data(
141 nsGkAtoms::aria_checked, eMixedType,
142 states::CHECKABLE, states::CHECKED);
143
144 MapTokenType(aElement, aState, data);
145 return true;
146 }
147
148 case eARIADisabled:
149 {
150 static const TokenTypeData data(
151 nsGkAtoms::aria_disabled, eBoolType,
152 0, states::UNAVAILABLE);
153
154 MapTokenType(aElement, aState, data);
155 return true;
156 }
157
158 case eARIAExpanded:
159 {
160 static const TokenTypeData data(
161 nsGkAtoms::aria_expanded, eBoolType,
162 0, states::EXPANDED, states::COLLAPSED);
163
164 MapTokenType(aElement, aState, data);
165 return true;
166 }
167
168 case eARIAHasPopup:
169 {
170 static const TokenTypeData data(
171 nsGkAtoms::aria_haspopup, eBoolType,
172 0, states::HASPOPUP);
173
174 MapTokenType(aElement, aState, data);
175 return true;
176 }
177
178 case eARIAInvalid:
179 {
180 static const TokenTypeData data(
181 nsGkAtoms::aria_invalid, eBoolType,
182 0, states::INVALID);
183
184 MapTokenType(aElement, aState, data);
185 return true;
186 }
187
188 case eARIAModal:
189 {
190 static const TokenTypeData data(
191 nsGkAtoms::aria_modal, eBoolType,
192 0, states::MODAL);
193
194 MapTokenType(aElement, aState, data);
195 return true;
196 }
197
198 case eARIAMultiline:
199 {
200 static const TokenTypeData data(
201 nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent,
202 0, states::MULTI_LINE, states::SINGLE_LINE);
203
204 MapTokenType(aElement, aState, data);
205 return true;
206 }
207
208 case eARIAMultiSelectable:
209 {
210 static const TokenTypeData data(
211 nsGkAtoms::aria_multiselectable, eBoolType,
212 0, states::MULTISELECTABLE | states::EXTSELECTABLE);
213
214 MapTokenType(aElement, aState, data);
215 return true;
216 }
217
218 case eARIAOrientation:
219 {
220 static const EnumTypeData data = {
221 nsGkAtoms::aria_orientation,
222 { &nsGkAtoms::horizontal,
223 &nsGkAtoms::vertical, nullptr },
224 { states::HORIZONTAL,
225 states::VERTICAL },
226 states::HORIZONTAL | states::VERTICAL
227 };
228
229 MapEnumType(aElement, aState, data);
230 return true;
231 }
232
233 case eARIAPressed:
234 {
235 static const TokenTypeData data(
236 nsGkAtoms::aria_pressed, eMixedType,
237 0, states::PRESSED);
238
239 MapTokenType(aElement, aState, data);
240 return true;
241 }
242
243 case eARIAReadonly:
244 {
245 static const TokenTypeData data(
246 nsGkAtoms::aria_readonly, eBoolType,
247 0, states::READONLY);
248
249 MapTokenType(aElement, aState, data);
250 return true;
251 }
252
253 case eARIAReadonlyOrEditable:
254 {
255 static const TokenTypeData data(
256 nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent,
257 0, states::READONLY, states::EDITABLE);
258
259 MapTokenType(aElement, aState, data);
260 return true;
261 }
262
263 case eARIAReadonlyOrEditableIfDefined:
264 {
265 static const TokenTypeData data(
266 nsGkAtoms::aria_readonly, eBoolType,
267 0, states::READONLY, states::EDITABLE);
268
269 MapTokenType(aElement, aState, data);
270 return true;
271 }
272
273 case eARIARequired:
274 {
275 static const TokenTypeData data(
276 nsGkAtoms::aria_required, eBoolType,
277 0, states::REQUIRED);
278
279 MapTokenType(aElement, aState, data);
280 return true;
281 }
282
283 case eARIASelectable:
284 {
285 static const TokenTypeData data(
286 nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent,
287 states::SELECTABLE, states::SELECTED);
288
289 MapTokenType(aElement, aState, data);
290 return true;
291 }
292
293 case eARIASelectableIfDefined:
294 {
295 static const TokenTypeData data(
296 nsGkAtoms::aria_selected, eBoolType,
297 states::SELECTABLE, states::SELECTED);
298
299 MapTokenType(aElement, aState, data);
300 return true;
301 }
302
303 case eReadonlyUntilEditable:
304 {
305 if (!(*aState & states::EDITABLE))
306 *aState |= states::READONLY;
307
308 return true;
309 }
310
311 case eIndeterminateIfNoValue:
312 {
313 if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
314 !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext))
315 *aState |= states::MIXED;
316
317 return true;
318 }
319
320 case eFocusableUntilDisabled:
321 {
322 if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) ||
323 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
324 nsGkAtoms::_false, eCaseMatters))
325 *aState |= states::FOCUSABLE;
326
327 return true;
328 }
329
330 default:
331 return false;
332 }
333 }
334
335 static void
MapEnumType(dom::Element * aElement,uint64_t * aState,const EnumTypeData & aData)336 MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData)
337 {
338 switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
339 aData.mValues, eCaseMatters)) {
340 case 0:
341 *aState = (*aState & ~aData.mClearState) | aData.mStates[0];
342 return;
343 case 1:
344 *aState = (*aState & ~aData.mClearState) | aData.mStates[1];
345 return;
346 case 2:
347 *aState = (*aState & ~aData.mClearState) | aData.mStates[2];
348 return;
349 }
350 }
351
352 static void
MapTokenType(dom::Element * aElement,uint64_t * aState,const TokenTypeData & aData)353 MapTokenType(dom::Element* aElement, uint64_t* aState,
354 const TokenTypeData& aData)
355 {
356 if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
357 if ((aData.mType & eMixedType) &&
358 aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
359 nsGkAtoms::mixed, eCaseMatters)) {
360 *aState |= aData.mPermanentState | states::MIXED;
361 return;
362 }
363
364 if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
365 nsGkAtoms::_false, eCaseMatters)) {
366 *aState |= aData.mPermanentState | aData.mFalseState;
367 return;
368 }
369
370 *aState |= aData.mPermanentState | aData.mTrueState;
371 return;
372 }
373
374 if (aData.mType & eDefinedIfAbsent)
375 *aState |= aData.mPermanentState | aData.mFalseState;
376 }
377