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