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