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