1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/base/ARIAStateMap.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,389 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "ARIAMap.h" 1.11 +#include "nsAccUtils.h" 1.12 +#include "States.h" 1.13 + 1.14 +#include "mozilla/dom/Element.h" 1.15 + 1.16 +using namespace mozilla; 1.17 +using namespace mozilla::a11y; 1.18 +using namespace mozilla::a11y::aria; 1.19 + 1.20 +/** 1.21 + * Used to store state map rule data for ARIA attribute of enum type. 1.22 + */ 1.23 +struct EnumTypeData 1.24 +{ 1.25 + EnumTypeData(nsIAtom* aAttrName, 1.26 + nsIAtom** aValue1, uint64_t aState1, 1.27 + nsIAtom** aValue2, uint64_t aState2, 1.28 + nsIAtom** aValue3 = 0, uint64_t aState3 = 0) : 1.29 + mState1(aState1), mState2(aState2), mState3(aState3), mDefaultState(0), 1.30 + mAttrName(aAttrName), mValue1(aValue1), mValue2(aValue2), mValue3(aValue3), 1.31 + mNullValue(nullptr) 1.32 + { } 1.33 + 1.34 + EnumTypeData(nsIAtom* aAttrName, uint64_t aDefaultState, 1.35 + nsIAtom** aValue1, uint64_t aState1) : 1.36 + mState1(aState1), mState2(0), mState3(0), mDefaultState(aDefaultState), 1.37 + mAttrName(aAttrName), mValue1(aValue1), mValue2(nullptr), mValue3(nullptr), 1.38 + mNullValue(nullptr) 1.39 + { } 1.40 + 1.41 + // States applied if corresponding enum values are matched. 1.42 + const uint64_t mState1; 1.43 + const uint64_t mState2; 1.44 + const uint64_t mState3; 1.45 + 1.46 + // Default state if no one enum value is matched. 1.47 + const uint64_t mDefaultState; 1.48 + 1.49 + // ARIA attribute name. 1.50 + nsIAtom* const mAttrName; 1.51 + 1.52 + // States if the attribute value is matched to the enum value. Used as 1.53 + // nsIContent::AttrValuesArray. 1.54 + nsIAtom* const* const mValue1; 1.55 + nsIAtom* const* const mValue2; 1.56 + nsIAtom* const* const mValue3; 1.57 + nsIAtom* const* const mNullValue; 1.58 +}; 1.59 + 1.60 +enum ETokenType 1.61 +{ 1.62 + eBoolType = 0, 1.63 + eMixedType = 1, // can take 'mixed' value 1.64 + eDefinedIfAbsent = 2 // permanent and false state are applied if absent 1.65 +}; 1.66 + 1.67 +/** 1.68 + * Used to store state map rule data for ARIA attribute of token type (including 1.69 + * mixed value). 1.70 + */ 1.71 +struct TokenTypeData 1.72 +{ 1.73 + TokenTypeData(nsIAtom* aAttrName, uint32_t aType, 1.74 + uint64_t aPermanentState, 1.75 + uint64_t aTrueState, 1.76 + uint64_t aFalseState = 0) : 1.77 + mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState), 1.78 + mTrueState(aTrueState), mFalseState(aFalseState) 1.79 + { } 1.80 + 1.81 + // ARIA attribute name. 1.82 + nsIAtom* const mAttrName; 1.83 + 1.84 + // Type. 1.85 + const uint32_t mType; 1.86 + 1.87 + // State applied if the attribute is defined or mType doesn't have 1.88 + // eDefinedIfAbsent flag set. 1.89 + const uint64_t mPermanentState; 1.90 + 1.91 + // States applied if the attribute value is true/false. 1.92 + const uint64_t mTrueState; 1.93 + const uint64_t mFalseState; 1.94 +}; 1.95 + 1.96 +/** 1.97 + * Map enum type attribute value to accessible state. 1.98 + */ 1.99 +static void MapEnumType(dom::Element* aElement, uint64_t* aState, 1.100 + const EnumTypeData& aData); 1.101 + 1.102 +/** 1.103 + * Map token type attribute value to states. 1.104 + */ 1.105 +static void MapTokenType(dom::Element* aContent, uint64_t* aState, 1.106 + const TokenTypeData& aData); 1.107 + 1.108 +bool 1.109 +aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState) 1.110 +{ 1.111 + switch (aRule) { 1.112 + case eARIAAutoComplete: 1.113 + { 1.114 + static const EnumTypeData data( 1.115 + nsGkAtoms::aria_autocomplete, 1.116 + &nsGkAtoms::inlinevalue, states::SUPPORTS_AUTOCOMPLETION, 1.117 + &nsGkAtoms::list, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION, 1.118 + &nsGkAtoms::both, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION); 1.119 + 1.120 + MapEnumType(aElement, aState, data); 1.121 + return true; 1.122 + } 1.123 + 1.124 + case eARIABusy: 1.125 + { 1.126 + static const EnumTypeData data( 1.127 + nsGkAtoms::aria_busy, 1.128 + &nsGkAtoms::_true, states::BUSY, 1.129 + &nsGkAtoms::error, states::INVALID); 1.130 + 1.131 + MapEnumType(aElement, aState, data); 1.132 + return true; 1.133 + } 1.134 + 1.135 + case eARIACheckableBool: 1.136 + { 1.137 + static const TokenTypeData data( 1.138 + nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent, 1.139 + states::CHECKABLE, states::CHECKED); 1.140 + 1.141 + MapTokenType(aElement, aState, data); 1.142 + return true; 1.143 + } 1.144 + 1.145 + case eARIACheckableMixed: 1.146 + { 1.147 + static const TokenTypeData data( 1.148 + nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent, 1.149 + states::CHECKABLE, states::CHECKED); 1.150 + 1.151 + MapTokenType(aElement, aState, data); 1.152 + return true; 1.153 + } 1.154 + 1.155 + case eARIACheckedMixed: 1.156 + { 1.157 + static const TokenTypeData data( 1.158 + nsGkAtoms::aria_checked, eMixedType, 1.159 + states::CHECKABLE, states::CHECKED); 1.160 + 1.161 + MapTokenType(aElement, aState, data); 1.162 + return true; 1.163 + } 1.164 + 1.165 + case eARIADisabled: 1.166 + { 1.167 + static const TokenTypeData data( 1.168 + nsGkAtoms::aria_disabled, eBoolType, 1.169 + 0, states::UNAVAILABLE); 1.170 + 1.171 + MapTokenType(aElement, aState, data); 1.172 + return true; 1.173 + } 1.174 + 1.175 + case eARIAExpanded: 1.176 + { 1.177 + static const TokenTypeData data( 1.178 + nsGkAtoms::aria_expanded, eBoolType, 1.179 + 0, states::EXPANDED, states::COLLAPSED); 1.180 + 1.181 + MapTokenType(aElement, aState, data); 1.182 + return true; 1.183 + } 1.184 + 1.185 + case eARIAHasPopup: 1.186 + { 1.187 + static const TokenTypeData data( 1.188 + nsGkAtoms::aria_haspopup, eBoolType, 1.189 + 0, states::HASPOPUP); 1.190 + 1.191 + MapTokenType(aElement, aState, data); 1.192 + return true; 1.193 + } 1.194 + 1.195 + case eARIAInvalid: 1.196 + { 1.197 + static const TokenTypeData data( 1.198 + nsGkAtoms::aria_invalid, eBoolType, 1.199 + 0, states::INVALID); 1.200 + 1.201 + MapTokenType(aElement, aState, data); 1.202 + return true; 1.203 + } 1.204 + 1.205 + case eARIAMultiline: 1.206 + { 1.207 + static const TokenTypeData data( 1.208 + nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent, 1.209 + 0, states::MULTI_LINE, states::SINGLE_LINE); 1.210 + 1.211 + MapTokenType(aElement, aState, data); 1.212 + return true; 1.213 + } 1.214 + 1.215 + case eARIAMultiSelectable: 1.216 + { 1.217 + static const TokenTypeData data( 1.218 + nsGkAtoms::aria_multiselectable, eBoolType, 1.219 + 0, states::MULTISELECTABLE | states::EXTSELECTABLE); 1.220 + 1.221 + MapTokenType(aElement, aState, data); 1.222 + return true; 1.223 + } 1.224 + 1.225 + case eARIAOrientation: 1.226 + { 1.227 + if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_orientation, 1.228 + NS_LITERAL_STRING("horizontal"), eCaseMatters)) { 1.229 + *aState &= ~states::VERTICAL; 1.230 + *aState |= states::HORIZONTAL; 1.231 + } else if (aElement->AttrValueIs(kNameSpaceID_None, 1.232 + nsGkAtoms::aria_orientation, 1.233 + NS_LITERAL_STRING("vertical"), 1.234 + eCaseMatters)) { 1.235 + *aState &= ~states::HORIZONTAL; 1.236 + *aState |= states::VERTICAL; 1.237 + } else { 1.238 + NS_ASSERTION(!(*aState & (states::HORIZONTAL | states::VERTICAL)), 1.239 + "orientation state on role with default aria-orientation!"); 1.240 + *aState |= GetRoleMap(aElement)->Is(nsGkAtoms::scrollbar) ? 1.241 + states::VERTICAL : states::HORIZONTAL; 1.242 + } 1.243 + 1.244 + return true; 1.245 + } 1.246 + 1.247 + case eARIAPressed: 1.248 + { 1.249 + static const TokenTypeData data( 1.250 + nsGkAtoms::aria_pressed, eMixedType, 1.251 + 0, states::PRESSED); 1.252 + 1.253 + MapTokenType(aElement, aState, data); 1.254 + return true; 1.255 + } 1.256 + 1.257 + case eARIAReadonly: 1.258 + { 1.259 + static const TokenTypeData data( 1.260 + nsGkAtoms::aria_readonly, eBoolType, 1.261 + 0, states::READONLY); 1.262 + 1.263 + MapTokenType(aElement, aState, data); 1.264 + return true; 1.265 + } 1.266 + 1.267 + case eARIAReadonlyOrEditable: 1.268 + { 1.269 + static const TokenTypeData data( 1.270 + nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent, 1.271 + 0, states::READONLY, states::EDITABLE); 1.272 + 1.273 + MapTokenType(aElement, aState, data); 1.274 + return true; 1.275 + } 1.276 + 1.277 + case eARIAReadonlyOrEditableIfDefined: 1.278 + { 1.279 + static const TokenTypeData data( 1.280 + nsGkAtoms::aria_readonly, eBoolType, 1.281 + 0, states::READONLY, states::EDITABLE); 1.282 + 1.283 + MapTokenType(aElement, aState, data); 1.284 + return true; 1.285 + } 1.286 + 1.287 + case eARIARequired: 1.288 + { 1.289 + static const TokenTypeData data( 1.290 + nsGkAtoms::aria_required, eBoolType, 1.291 + 0, states::REQUIRED); 1.292 + 1.293 + MapTokenType(aElement, aState, data); 1.294 + return true; 1.295 + } 1.296 + 1.297 + case eARIASelectable: 1.298 + { 1.299 + static const TokenTypeData data( 1.300 + nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent, 1.301 + states::SELECTABLE, states::SELECTED); 1.302 + 1.303 + MapTokenType(aElement, aState, data); 1.304 + return true; 1.305 + } 1.306 + 1.307 + case eARIASelectableIfDefined: 1.308 + { 1.309 + static const TokenTypeData data( 1.310 + nsGkAtoms::aria_selected, eBoolType, 1.311 + states::SELECTABLE, states::SELECTED); 1.312 + 1.313 + MapTokenType(aElement, aState, data); 1.314 + return true; 1.315 + } 1.316 + 1.317 + case eReadonlyUntilEditable: 1.318 + { 1.319 + if (!(*aState & states::EDITABLE)) 1.320 + *aState |= states::READONLY; 1.321 + 1.322 + return true; 1.323 + } 1.324 + 1.325 + case eIndeterminateIfNoValue: 1.326 + { 1.327 + if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) && 1.328 + !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext)) 1.329 + *aState |= states::MIXED; 1.330 + 1.331 + return true; 1.332 + } 1.333 + 1.334 + case eFocusableUntilDisabled: 1.335 + { 1.336 + if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) || 1.337 + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, 1.338 + nsGkAtoms::_false, eCaseMatters)) 1.339 + *aState |= states::FOCUSABLE; 1.340 + 1.341 + return true; 1.342 + } 1.343 + 1.344 + default: 1.345 + return false; 1.346 + } 1.347 +} 1.348 + 1.349 +static void 1.350 +MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData) 1.351 +{ 1.352 + switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName, 1.353 + &aData.mValue1, eCaseMatters)) { 1.354 + case 0: 1.355 + *aState |= aData.mState1; 1.356 + return; 1.357 + case 1: 1.358 + *aState |= aData.mState2; 1.359 + return; 1.360 + case 2: 1.361 + *aState |= aData.mState3; 1.362 + return; 1.363 + } 1.364 + 1.365 + *aState |= aData.mDefaultState; 1.366 +} 1.367 + 1.368 +static void 1.369 +MapTokenType(dom::Element* aElement, uint64_t* aState, 1.370 + const TokenTypeData& aData) 1.371 +{ 1.372 + if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) { 1.373 + if ((aData.mType & eMixedType) && 1.374 + aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, 1.375 + nsGkAtoms::mixed, eCaseMatters)) { 1.376 + *aState |= aData.mPermanentState | states::MIXED; 1.377 + return; 1.378 + } 1.379 + 1.380 + if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, 1.381 + nsGkAtoms::_false, eCaseMatters)) { 1.382 + *aState |= aData.mPermanentState | aData.mFalseState; 1.383 + return; 1.384 + } 1.385 + 1.386 + *aState |= aData.mPermanentState | aData.mTrueState; 1.387 + return; 1.388 + } 1.389 + 1.390 + if (aData.mType & eDefinedIfAbsent) 1.391 + *aState |= aData.mPermanentState | aData.mFalseState; 1.392 +}