michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ARIAMap.h" michael@0: #include "nsAccUtils.h" michael@0: #include "States.h" michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: using namespace mozilla::a11y::aria; michael@0: michael@0: /** michael@0: * Used to store state map rule data for ARIA attribute of enum type. michael@0: */ michael@0: struct EnumTypeData michael@0: { michael@0: EnumTypeData(nsIAtom* aAttrName, michael@0: nsIAtom** aValue1, uint64_t aState1, michael@0: nsIAtom** aValue2, uint64_t aState2, michael@0: nsIAtom** aValue3 = 0, uint64_t aState3 = 0) : michael@0: mState1(aState1), mState2(aState2), mState3(aState3), mDefaultState(0), michael@0: mAttrName(aAttrName), mValue1(aValue1), mValue2(aValue2), mValue3(aValue3), michael@0: mNullValue(nullptr) michael@0: { } michael@0: michael@0: EnumTypeData(nsIAtom* aAttrName, uint64_t aDefaultState, michael@0: nsIAtom** aValue1, uint64_t aState1) : michael@0: mState1(aState1), mState2(0), mState3(0), mDefaultState(aDefaultState), michael@0: mAttrName(aAttrName), mValue1(aValue1), mValue2(nullptr), mValue3(nullptr), michael@0: mNullValue(nullptr) michael@0: { } michael@0: michael@0: // States applied if corresponding enum values are matched. michael@0: const uint64_t mState1; michael@0: const uint64_t mState2; michael@0: const uint64_t mState3; michael@0: michael@0: // Default state if no one enum value is matched. michael@0: const uint64_t mDefaultState; michael@0: michael@0: // ARIA attribute name. michael@0: nsIAtom* const mAttrName; michael@0: michael@0: // States if the attribute value is matched to the enum value. Used as michael@0: // nsIContent::AttrValuesArray. michael@0: nsIAtom* const* const mValue1; michael@0: nsIAtom* const* const mValue2; michael@0: nsIAtom* const* const mValue3; michael@0: nsIAtom* const* const mNullValue; michael@0: }; michael@0: michael@0: enum ETokenType michael@0: { michael@0: eBoolType = 0, michael@0: eMixedType = 1, // can take 'mixed' value michael@0: eDefinedIfAbsent = 2 // permanent and false state are applied if absent michael@0: }; michael@0: michael@0: /** michael@0: * Used to store state map rule data for ARIA attribute of token type (including michael@0: * mixed value). michael@0: */ michael@0: struct TokenTypeData michael@0: { michael@0: TokenTypeData(nsIAtom* aAttrName, uint32_t aType, michael@0: uint64_t aPermanentState, michael@0: uint64_t aTrueState, michael@0: uint64_t aFalseState = 0) : michael@0: mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState), michael@0: mTrueState(aTrueState), mFalseState(aFalseState) michael@0: { } michael@0: michael@0: // ARIA attribute name. michael@0: nsIAtom* const mAttrName; michael@0: michael@0: // Type. michael@0: const uint32_t mType; michael@0: michael@0: // State applied if the attribute is defined or mType doesn't have michael@0: // eDefinedIfAbsent flag set. michael@0: const uint64_t mPermanentState; michael@0: michael@0: // States applied if the attribute value is true/false. michael@0: const uint64_t mTrueState; michael@0: const uint64_t mFalseState; michael@0: }; michael@0: michael@0: /** michael@0: * Map enum type attribute value to accessible state. michael@0: */ michael@0: static void MapEnumType(dom::Element* aElement, uint64_t* aState, michael@0: const EnumTypeData& aData); michael@0: michael@0: /** michael@0: * Map token type attribute value to states. michael@0: */ michael@0: static void MapTokenType(dom::Element* aContent, uint64_t* aState, michael@0: const TokenTypeData& aData); michael@0: michael@0: bool michael@0: aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState) michael@0: { michael@0: switch (aRule) { michael@0: case eARIAAutoComplete: michael@0: { michael@0: static const EnumTypeData data( michael@0: nsGkAtoms::aria_autocomplete, michael@0: &nsGkAtoms::inlinevalue, states::SUPPORTS_AUTOCOMPLETION, michael@0: &nsGkAtoms::list, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION, michael@0: &nsGkAtoms::both, states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION); michael@0: michael@0: MapEnumType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIABusy: michael@0: { michael@0: static const EnumTypeData data( michael@0: nsGkAtoms::aria_busy, michael@0: &nsGkAtoms::_true, states::BUSY, michael@0: &nsGkAtoms::error, states::INVALID); michael@0: michael@0: MapEnumType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIACheckableBool: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent, michael@0: states::CHECKABLE, states::CHECKED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIACheckableMixed: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent, michael@0: states::CHECKABLE, states::CHECKED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIACheckedMixed: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_checked, eMixedType, michael@0: states::CHECKABLE, states::CHECKED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIADisabled: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_disabled, eBoolType, michael@0: 0, states::UNAVAILABLE); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAExpanded: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_expanded, eBoolType, michael@0: 0, states::EXPANDED, states::COLLAPSED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAHasPopup: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_haspopup, eBoolType, michael@0: 0, states::HASPOPUP); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAInvalid: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_invalid, eBoolType, michael@0: 0, states::INVALID); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAMultiline: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent, michael@0: 0, states::MULTI_LINE, states::SINGLE_LINE); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAMultiSelectable: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_multiselectable, eBoolType, michael@0: 0, states::MULTISELECTABLE | states::EXTSELECTABLE); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAOrientation: michael@0: { michael@0: if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_orientation, michael@0: NS_LITERAL_STRING("horizontal"), eCaseMatters)) { michael@0: *aState &= ~states::VERTICAL; michael@0: *aState |= states::HORIZONTAL; michael@0: } else if (aElement->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::aria_orientation, michael@0: NS_LITERAL_STRING("vertical"), michael@0: eCaseMatters)) { michael@0: *aState &= ~states::HORIZONTAL; michael@0: *aState |= states::VERTICAL; michael@0: } else { michael@0: NS_ASSERTION(!(*aState & (states::HORIZONTAL | states::VERTICAL)), michael@0: "orientation state on role with default aria-orientation!"); michael@0: *aState |= GetRoleMap(aElement)->Is(nsGkAtoms::scrollbar) ? michael@0: states::VERTICAL : states::HORIZONTAL; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: case eARIAPressed: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_pressed, eMixedType, michael@0: 0, states::PRESSED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAReadonly: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_readonly, eBoolType, michael@0: 0, states::READONLY); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAReadonlyOrEditable: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent, michael@0: 0, states::READONLY, states::EDITABLE); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIAReadonlyOrEditableIfDefined: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_readonly, eBoolType, michael@0: 0, states::READONLY, states::EDITABLE); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIARequired: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_required, eBoolType, michael@0: 0, states::REQUIRED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIASelectable: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent, michael@0: states::SELECTABLE, states::SELECTED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eARIASelectableIfDefined: michael@0: { michael@0: static const TokenTypeData data( michael@0: nsGkAtoms::aria_selected, eBoolType, michael@0: states::SELECTABLE, states::SELECTED); michael@0: michael@0: MapTokenType(aElement, aState, data); michael@0: return true; michael@0: } michael@0: michael@0: case eReadonlyUntilEditable: michael@0: { michael@0: if (!(*aState & states::EDITABLE)) michael@0: *aState |= states::READONLY; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: case eIndeterminateIfNoValue: michael@0: { michael@0: if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) && michael@0: !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext)) michael@0: *aState |= states::MIXED; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: case eFocusableUntilDisabled: michael@0: { michael@0: if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) || michael@0: aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, michael@0: nsGkAtoms::_false, eCaseMatters)) michael@0: *aState |= states::FOCUSABLE; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData) michael@0: { michael@0: switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName, michael@0: &aData.mValue1, eCaseMatters)) { michael@0: case 0: michael@0: *aState |= aData.mState1; michael@0: return; michael@0: case 1: michael@0: *aState |= aData.mState2; michael@0: return; michael@0: case 2: michael@0: *aState |= aData.mState3; michael@0: return; michael@0: } michael@0: michael@0: *aState |= aData.mDefaultState; michael@0: } michael@0: michael@0: static void michael@0: MapTokenType(dom::Element* aElement, uint64_t* aState, michael@0: const TokenTypeData& aData) michael@0: { michael@0: if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) { michael@0: if ((aData.mType & eMixedType) && michael@0: aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, michael@0: nsGkAtoms::mixed, eCaseMatters)) { michael@0: *aState |= aData.mPermanentState | states::MIXED; michael@0: return; michael@0: } michael@0: michael@0: if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, michael@0: nsGkAtoms::_false, eCaseMatters)) { michael@0: *aState |= aData.mPermanentState | aData.mFalseState; michael@0: return; michael@0: } michael@0: michael@0: *aState |= aData.mPermanentState | aData.mTrueState; michael@0: return; michael@0: } michael@0: michael@0: if (aData.mType & eDefinedIfAbsent) michael@0: *aState |= aData.mPermanentState | aData.mFalseState; michael@0: }