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