From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- accessible/base/ARIAMap.cpp | 1008 +++++++++++++++ accessible/base/ARIAMap.h | 305 +++++ accessible/base/ARIAStateMap.cpp | 376 ++++++ accessible/base/ARIAStateMap.h | 67 + accessible/base/AccEvent.cpp | 271 ++++ accessible/base/AccEvent.h | 570 +++++++++ accessible/base/AccGroupInfo.cpp | 227 ++++ accessible/base/AccGroupInfo.h | 114 ++ accessible/base/AccIterator.cpp | 414 ++++++ accessible/base/AccIterator.h | 325 +++++ accessible/base/AccTypes.h | 94 ++ accessible/base/AccessibleOrProxy.cpp | 27 + accessible/base/AccessibleOrProxy.h | 123 ++ accessible/base/Asserts.cpp | 26 + accessible/base/DocManager.cpp | 594 +++++++++ accessible/base/DocManager.h | 189 +++ accessible/base/EmbeddedObjCollector.cpp | 81 ++ accessible/base/EmbeddedObjCollector.h | 69 + accessible/base/EventQueue.cpp | 344 +++++ accessible/base/EventQueue.h | 77 ++ accessible/base/EventTree.cpp | 618 +++++++++ accessible/base/EventTree.h | 121 ++ accessible/base/Filters.cpp | 51 + accessible/base/Filters.h | 50 + accessible/base/FocusManager.cpp | 404 ++++++ accessible/base/FocusManager.h | 132 ++ accessible/base/Logging.cpp | 1039 +++++++++++++++ accessible/base/Logging.h | 225 ++++ accessible/base/MarkupMap.h | 340 +++++ accessible/base/NotificationController.cpp | 947 ++++++++++++++ accessible/base/NotificationController.h | 439 +++++++ accessible/base/Platform.h | 88 ++ accessible/base/Relation.h | 108 ++ accessible/base/RelationType.h | 163 +++ accessible/base/RelationTypeMap.h | 154 +++ accessible/base/Role.h | 995 +++++++++++++++ accessible/base/RoleMap.h | 1370 ++++++++++++++++++++ accessible/base/SelectionManager.cpp | 232 ++++ accessible/base/SelectionManager.h | 133 ++ accessible/base/States.h | 285 +++++ accessible/base/Statistics.h | 39 + accessible/base/StyleInfo.cpp | 122 ++ accessible/base/StyleInfo.h | 48 + accessible/base/TextAttrs.cpp | 913 ++++++++++++++ accessible/base/TextAttrs.h | 478 +++++++ accessible/base/TextRange-inl.h | 29 + accessible/base/TextRange.cpp | 380 ++++++ accessible/base/TextRange.h | 270 ++++ accessible/base/TextUpdater.cpp | 202 +++ accessible/base/TextUpdater.h | 96 ++ accessible/base/TreeWalker.cpp | 325 +++++ accessible/base/TreeWalker.h | 144 +++ accessible/base/moz.build | 114 ++ accessible/base/nsAccCache.h | 28 + accessible/base/nsAccUtils.cpp | 447 +++++++ accessible/base/nsAccUtils.h | 243 ++++ accessible/base/nsAccessibilityService.cpp | 1885 ++++++++++++++++++++++++++++ accessible/base/nsAccessibilityService.h | 442 +++++++ accessible/base/nsAccessiblePivot.cpp | 924 ++++++++++++++ accessible/base/nsAccessiblePivot.h | 144 +++ accessible/base/nsCoreUtils.cpp | 683 ++++++++++ accessible/base/nsCoreUtils.h | 329 +++++ accessible/base/nsEventShell.cpp | 79 ++ accessible/base/nsEventShell.h | 67 + accessible/base/nsTextEquivUtils.cpp | 364 ++++++ accessible/base/nsTextEquivUtils.h | 162 +++ 66 files changed, 22152 insertions(+) create mode 100644 accessible/base/ARIAMap.cpp create mode 100644 accessible/base/ARIAMap.h create mode 100644 accessible/base/ARIAStateMap.cpp create mode 100644 accessible/base/ARIAStateMap.h create mode 100644 accessible/base/AccEvent.cpp create mode 100644 accessible/base/AccEvent.h create mode 100644 accessible/base/AccGroupInfo.cpp create mode 100644 accessible/base/AccGroupInfo.h create mode 100644 accessible/base/AccIterator.cpp create mode 100644 accessible/base/AccIterator.h create mode 100644 accessible/base/AccTypes.h create mode 100644 accessible/base/AccessibleOrProxy.cpp create mode 100644 accessible/base/AccessibleOrProxy.h create mode 100644 accessible/base/Asserts.cpp create mode 100644 accessible/base/DocManager.cpp create mode 100644 accessible/base/DocManager.h create mode 100644 accessible/base/EmbeddedObjCollector.cpp create mode 100644 accessible/base/EmbeddedObjCollector.h create mode 100644 accessible/base/EventQueue.cpp create mode 100644 accessible/base/EventQueue.h create mode 100644 accessible/base/EventTree.cpp create mode 100644 accessible/base/EventTree.h create mode 100644 accessible/base/Filters.cpp create mode 100644 accessible/base/Filters.h create mode 100644 accessible/base/FocusManager.cpp create mode 100644 accessible/base/FocusManager.h create mode 100644 accessible/base/Logging.cpp create mode 100644 accessible/base/Logging.h create mode 100644 accessible/base/MarkupMap.h create mode 100644 accessible/base/NotificationController.cpp create mode 100644 accessible/base/NotificationController.h create mode 100644 accessible/base/Platform.h create mode 100644 accessible/base/Relation.h create mode 100644 accessible/base/RelationType.h create mode 100644 accessible/base/RelationTypeMap.h create mode 100644 accessible/base/Role.h create mode 100644 accessible/base/RoleMap.h create mode 100644 accessible/base/SelectionManager.cpp create mode 100644 accessible/base/SelectionManager.h create mode 100644 accessible/base/States.h create mode 100644 accessible/base/Statistics.h create mode 100644 accessible/base/StyleInfo.cpp create mode 100644 accessible/base/StyleInfo.h create mode 100644 accessible/base/TextAttrs.cpp create mode 100644 accessible/base/TextAttrs.h create mode 100644 accessible/base/TextRange-inl.h create mode 100644 accessible/base/TextRange.cpp create mode 100644 accessible/base/TextRange.h create mode 100644 accessible/base/TextUpdater.cpp create mode 100644 accessible/base/TextUpdater.h create mode 100644 accessible/base/TreeWalker.cpp create mode 100644 accessible/base/TreeWalker.h create mode 100644 accessible/base/moz.build create mode 100644 accessible/base/nsAccCache.h create mode 100644 accessible/base/nsAccUtils.cpp create mode 100644 accessible/base/nsAccUtils.h create mode 100644 accessible/base/nsAccessibilityService.cpp create mode 100644 accessible/base/nsAccessibilityService.h create mode 100644 accessible/base/nsAccessiblePivot.cpp create mode 100644 accessible/base/nsAccessiblePivot.h create mode 100644 accessible/base/nsCoreUtils.cpp create mode 100644 accessible/base/nsCoreUtils.h create mode 100644 accessible/base/nsEventShell.cpp create mode 100644 accessible/base/nsEventShell.h create mode 100644 accessible/base/nsTextEquivUtils.cpp create mode 100644 accessible/base/nsTextEquivUtils.h (limited to 'accessible/base') diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp new file mode 100644 index 000000000..c29d37873 --- /dev/null +++ b/accessible/base/ARIAMap.cpp @@ -0,0 +1,1008 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ARIAMap.h" + +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "Role.h" +#include "States.h" + +#include "nsAttrName.h" +#include "nsWhitespaceTokenizer.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; +using namespace mozilla::a11y::aria; + +static const uint32_t kGenericAccType = 0; + +/** + * This list of WAI-defined roles are currently hardcoded. + * Eventually we will most likely be loading an RDF resource that contains this information + * Using RDF will also allow for role extensibility. See bug 280138. + * + * Definition of nsRoleMapEntry contains comments explaining this table. + * + * When no Role enum mapping exists for an ARIA role, the role will be exposed + * via the object attribute "xml-roles". + */ + +static const nsRoleMapEntry sWAIRoleMaps[] = +{ + { // alert + &nsGkAtoms::alert, + roles::ALERT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eAlert, + kNoReqStates + }, + { // alertdialog + &nsGkAtoms::alertdialog, + roles::DIALOG, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // application + &nsGkAtoms::application, + roles::APPLICATION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // article + &nsGkAtoms::article, + roles::DOCUMENT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eReadonlyUntilEditable + }, + { // banner + &nsGkAtoms::banner, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // button + &nsGkAtoms::button, + roles::PUSHBUTTON, + kUseMapRole, + eNoValue, + ePressAction, + eNoLiveAttr, + eButton, + kNoReqStates + // eARIAPressed is auto applied on any button + }, + { // cell + &nsGkAtoms::cell, + roles::CELL, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTableCell, + kNoReqStates + }, + { // checkbox + &nsGkAtoms::checkbox, + roles::CHECKBUTTON, + kUseMapRole, + eNoValue, + eCheckUncheckAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableMixed, + eARIAReadonly + }, + { // columnheader + &nsGkAtoms::columnheader, + roles::COLUMNHEADER, + kUseMapRole, + eNoValue, + eSortAction, + eNoLiveAttr, + eTableCell, + kNoReqStates, + eARIASelectableIfDefined, + eARIAReadonlyOrEditableIfDefined + }, + { // combobox + &nsGkAtoms::combobox, + roles::COMBOBOX, + kUseMapRole, + eNoValue, + eOpenCloseAction, + eNoLiveAttr, + kGenericAccType, + states::COLLAPSED | states::HASPOPUP | states::VERTICAL, + eARIAAutoComplete, + eARIAReadonly, + eARIAOrientation + }, + { // complementary + &nsGkAtoms::complementary, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // contentinfo + &nsGkAtoms::contentinfo, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // dialog + &nsGkAtoms::dialog, + roles::DIALOG, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // directory + &nsGkAtoms::directory, + roles::LIST, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eList, + kNoReqStates + }, + { // document + &nsGkAtoms::document, + roles::DOCUMENT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eReadonlyUntilEditable + }, + { // feed + &nsGkAtoms::feed, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // form + &nsGkAtoms::form, + roles::FORM, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // grid + &nsGkAtoms::grid, + roles::TABLE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eSelect | eTable, + kNoReqStates, + eARIAMultiSelectable, + eARIAReadonlyOrEditable, + eFocusableUntilDisabled + }, + { // gridcell + &nsGkAtoms::gridcell, + roles::GRID_CELL, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTableCell, + kNoReqStates, + eARIASelectable, + eARIAReadonlyOrEditableIfDefined + }, + { // group + &nsGkAtoms::group, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // heading + &nsGkAtoms::heading, + roles::HEADING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // img + &nsGkAtoms::img, + roles::GRAPHIC, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // key + &nsGkAtoms::key, + roles::KEY, + kUseMapRole, + eNoValue, + ePressAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAPressed + }, + { // link + &nsGkAtoms::link, + roles::LINK, + kUseMapRole, + eNoValue, + eJumpAction, + eNoLiveAttr, + kGenericAccType, + states::LINKED + }, + { // list + &nsGkAtoms::list, + roles::LIST, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eList, + states::READONLY + }, + { // listbox + &nsGkAtoms::listbox, + roles::LISTBOX, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eListControl | eSelect, + states::VERTICAL, + eARIAMultiSelectable, + eARIAReadonly, + eFocusableUntilDisabled, + eARIAOrientation + }, + { // listitem + &nsGkAtoms::listitem, + roles::LISTITEM, + kUseMapRole, + eNoValue, + eNoAction, // XXX: should depend on state, parent accessible + eNoLiveAttr, + kGenericAccType, + states::READONLY + }, + { // log + &nsGkAtoms::log_, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + ePoliteLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // main + &nsGkAtoms::main, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // marquee + &nsGkAtoms::marquee, + roles::ANIMATION, + kUseMapRole, + eNoValue, + eNoAction, + eOffLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // math + &nsGkAtoms::math, + roles::FLAT_EQUATION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // menu + &nsGkAtoms::menu, + roles::MENUPOPUP, + kUseMapRole, + eNoValue, + eNoAction, // XXX: technically accessibles of menupopup role haven't + // any action, but menu can be open or close. + eNoLiveAttr, + kGenericAccType, + states::VERTICAL, + eARIAOrientation + }, + { // menubar + &nsGkAtoms::menubar, + roles::MENUBAR, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::HORIZONTAL, + eARIAOrientation + }, + { // menuitem + &nsGkAtoms::menuitem, + roles::MENUITEM, + kUseMapRole, + eNoValue, + eClickAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckedMixed + }, + { // menuitemcheckbox + &nsGkAtoms::menuitemcheckbox, + roles::CHECK_MENU_ITEM, + kUseMapRole, + eNoValue, + eClickAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableMixed + }, + { // menuitemradio + &nsGkAtoms::menuitemradio, + roles::RADIO_MENU_ITEM, + kUseMapRole, + eNoValue, + eClickAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableBool + }, + { // navigation + &nsGkAtoms::navigation, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // none + &nsGkAtoms::none, + roles::NOTHING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // note + &nsGkAtoms::note_, + roles::NOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // option + &nsGkAtoms::option, + roles::OPTION, + kUseMapRole, + eNoValue, + eSelectAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIASelectable, + eARIACheckedMixed + }, + { // presentation + &nsGkAtoms::presentation, + roles::NOTHING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // progressbar + &nsGkAtoms::progressbar, + roles::PROGRESSBAR, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::READONLY, + eIndeterminateIfNoValue + }, + { // radio + &nsGkAtoms::radio, + roles::RADIOBUTTON, + kUseMapRole, + eNoValue, + eSelectAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableBool + }, + { // radiogroup + &nsGkAtoms::radiogroup, + roles::RADIO_GROUP, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAOrientation + }, + { // region + &nsGkAtoms::region, + roles::PANE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // row + &nsGkAtoms::row, + roles::ROW, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTableRow, + kNoReqStates, + eARIASelectable + }, + { // rowgroup + &nsGkAtoms::rowgroup, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // rowheader + &nsGkAtoms::rowheader, + roles::ROWHEADER, + kUseMapRole, + eNoValue, + eSortAction, + eNoLiveAttr, + eTableCell, + kNoReqStates, + eARIASelectableIfDefined, + eARIAReadonlyOrEditableIfDefined + }, + { // scrollbar + &nsGkAtoms::scrollbar, + roles::SCROLLBAR, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::VERTICAL, + eARIAOrientation, + eARIAReadonly + }, + { // search + &nsGkAtoms::search, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // searchbox + &nsGkAtoms::searchbox, + roles::ENTRY, + kUseMapRole, + eNoValue, + eActivateAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAAutoComplete, + eARIAMultiline, + eARIAReadonlyOrEditable + }, + { // separator + &nsGkAtoms::separator_, + roles::SEPARATOR, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::HORIZONTAL, + eARIAOrientation + }, + { // slider + &nsGkAtoms::slider, + roles::SLIDER, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::HORIZONTAL, + eARIAOrientation, + eARIAReadonly + }, + { // spinbutton + &nsGkAtoms::spinbutton, + roles::SPINBUTTON, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAReadonly + }, + { // status + &nsGkAtoms::status, + roles::STATUSBAR, + kUseMapRole, + eNoValue, + eNoAction, + ePoliteLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // switch + &nsGkAtoms::_switch, + roles::SWITCH, + kUseMapRole, + eNoValue, + eCheckUncheckAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableBool + }, + { // tab + &nsGkAtoms::tab, + roles::PAGETAB, + kUseMapRole, + eNoValue, + eSwitchAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIASelectable + }, + { // table + &nsGkAtoms::table, + roles::TABLE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTable, + kNoReqStates, + eARIASelectable + }, + { // tablist + &nsGkAtoms::tablist, + roles::PAGETABLIST, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eSelect, + states::HORIZONTAL, + eARIAOrientation + }, + { // tabpanel + &nsGkAtoms::tabpanel, + roles::PROPERTYPAGE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // textbox + &nsGkAtoms::textbox, + roles::ENTRY, + kUseMapRole, + eNoValue, + eActivateAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAAutoComplete, + eARIAMultiline, + eARIAReadonlyOrEditable + }, + { // timer + &nsGkAtoms::timer, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eOffLiveAttr, + kNoReqStates + }, + { // toolbar + &nsGkAtoms::toolbar, + roles::TOOLBAR, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::HORIZONTAL, + eARIAOrientation + }, + { // tooltip + &nsGkAtoms::tooltip, + roles::TOOLTIP, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // tree + &nsGkAtoms::tree, + roles::OUTLINE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eSelect, + states::VERTICAL, + eARIAReadonly, + eARIAMultiSelectable, + eFocusableUntilDisabled, + eARIAOrientation + }, + { // treegrid + &nsGkAtoms::treegrid, + roles::TREE_TABLE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eSelect | eTable, + states::VERTICAL, + eARIAReadonlyOrEditable, + eARIAMultiSelectable, + eFocusableUntilDisabled, + eARIAOrientation + }, + { // treeitem + &nsGkAtoms::treeitem, + roles::OUTLINEITEM, + kUseMapRole, + eNoValue, + eActivateAction, // XXX: should expose second 'expand/collapse' action based + // on states + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIASelectable, + eARIACheckedMixed + } +}; + +static const nsRoleMapEntry sLandmarkRoleMap = { + &nsGkAtoms::_empty, + roles::NOTHING, + kUseNativeRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates +}; + +nsRoleMapEntry aria::gEmptyRoleMap = { + &nsGkAtoms::_empty, + roles::NOTHING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates +}; + +/** + * Universal (Global) states: + * The following state rules are applied to any accessible element, + * whether there is an ARIA role or not: + */ +static const EStateRule sWAIUnivStateMap[] = { + eARIABusy, + eARIADisabled, + eARIAExpanded, // Currently under spec review but precedent exists + eARIAHasPopup, // Note this is technically a "property" + eARIAInvalid, + eARIAModal, + eARIARequired, // XXX not global, Bug 553117 + eARIANone +}; + + +/** + * ARIA attribute map for attribute characteristics. + * @note ARIA attributes that don't have any flags are not included here. + */ + +struct AttrCharacteristics +{ + nsIAtom** attributeName; + const uint8_t characteristics; +}; + +static const AttrCharacteristics gWAIUnivAttrMap[] = { + {&nsGkAtoms::aria_activedescendant, ATTR_BYPASSOBJ }, + {&nsGkAtoms::aria_atomic, ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_busy, ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_checked, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, /* exposes checkable obj attr */ + {&nsGkAtoms::aria_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_details, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_errormessage, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_grabbed, ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_hidden, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, /* handled special way */ + {&nsGkAtoms::aria_invalid, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_label, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_labelledby, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_level, ATTR_BYPASSOBJ }, /* handled via groupPosition */ + {&nsGkAtoms::aria_live, ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_modal, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, + {&nsGkAtoms::aria_multiline, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_multiselectable, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_owns, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_orientation, ATTR_VALTOKEN }, + {&nsGkAtoms::aria_posinset, ATTR_BYPASSOBJ }, /* handled via groupPosition */ + {&nsGkAtoms::aria_pressed, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_readonly, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_relevant, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {&nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {&nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */ + {&nsGkAtoms::aria_sort, ATTR_VALTOKEN }, + {&nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ }, + {&nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ }, + {&nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ }, + {&nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ } +}; + +namespace { + +struct RoleComparator +{ + const nsDependentSubstring& mRole; + explicit RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {} + int operator()(const nsRoleMapEntry& aEntry) const { + return Compare(mRole, aEntry.ARIARoleString()); + } +}; + +} + +const nsRoleMapEntry* +aria::GetRoleMap(dom::Element* aEl) +{ + return GetRoleMapFromIndex(GetRoleMapIndex(aEl)); +} + +uint8_t +aria::GetRoleMapIndex(dom::Element* aEl) +{ + nsAutoString roles; + if (!aEl || !aEl->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roles) || + roles.IsEmpty()) { + // We treat role="" as if the role attribute is absent (per aria spec:8.1.1) + return NO_ROLE_MAP_ENTRY_INDEX; + } + + nsWhitespaceTokenizer tokenizer(roles); + while (tokenizer.hasMoreTokens()) { + // Do a binary search through table for the next role in role list + const nsDependentSubstring role = tokenizer.nextToken(); + size_t idx; + if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps), + RoleComparator(role), &idx)) { + return idx; + } + } + + // Always use some entry index if there is a non-empty role string + // To ensure an accessible object is created + return LANDMARK_ROLE_MAP_ENTRY_INDEX; +} + + +const nsRoleMapEntry* +aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) +{ + switch (aRoleMapIndex) { + case NO_ROLE_MAP_ENTRY_INDEX: + return nullptr; + case EMPTY_ROLE_MAP_ENTRY_INDEX: + return &gEmptyRoleMap; + case LANDMARK_ROLE_MAP_ENTRY_INDEX: + return &sLandmarkRoleMap; + default: + return sWAIRoleMaps + aRoleMapIndex; + } +} + +uint8_t +aria::GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMapEntry) +{ + if (aRoleMapEntry == nullptr) { + return NO_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &gEmptyRoleMap) { + return EMPTY_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &sLandmarkRoleMap) { + return LANDMARK_ROLE_MAP_ENTRY_INDEX; + } else { + return aRoleMapEntry - sWAIRoleMaps; + } +} + +uint64_t +aria::UniversalStatesFor(mozilla::dom::Element* aElement) +{ + uint64_t state = 0; + uint32_t index = 0; + while (MapToState(sWAIUnivStateMap[index], aElement, &state)) + index++; + + return state; +} + +uint8_t +aria::AttrCharacteristicsFor(nsIAtom* aAtom) +{ + for (uint32_t i = 0; i < ArrayLength(gWAIUnivAttrMap); i++) + if (*gWAIUnivAttrMap[i].attributeName == aAtom) + return gWAIUnivAttrMap[i].characteristics; + + return 0; +} + +bool +aria::HasDefinedARIAHidden(nsIContent* aContent) +{ + return aContent && + nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_hidden) && + !aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_hidden, + nsGkAtoms::_false, eCaseMatters); +} + +//////////////////////////////////////////////////////////////////////////////// +// AttrIterator class + +bool +AttrIterator::Next(nsAString& aAttrName, nsAString& aAttrValue) +{ + while (mAttrIdx < mAttrCount) { + const nsAttrName* attr = mContent->GetAttrNameAt(mAttrIdx); + mAttrIdx++; + if (attr->NamespaceEquals(kNameSpaceID_None)) { + nsIAtom* attrAtom = attr->Atom(); + nsDependentAtomString attrStr(attrAtom); + if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-"))) + continue; // Not ARIA + + uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom); + if (attrFlags & ATTR_BYPASSOBJ) + continue; // No need to handle exposing as obj attribute here + + if ((attrFlags & ATTR_VALTOKEN) && + !nsAccUtils::HasDefinedARIAToken(mContent, attrAtom)) + continue; // only expose token based attributes if they are defined + + if ((attrFlags & ATTR_BYPASSOBJ_IF_FALSE) && + mContent->AttrValueIs(kNameSpaceID_None, attrAtom, + nsGkAtoms::_false, eCaseMatters)) { + continue; // only expose token based attribute if value is not 'false'. + } + + nsAutoString value; + if (mContent->GetAttr(kNameSpaceID_None, attrAtom, value)) { + aAttrName.Assign(Substring(attrStr, 5)); + aAttrValue.Assign(value); + return true; + } + } + } + + return false; +} + diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h new file mode 100644 index 000000000..4d603c04b --- /dev/null +++ b/accessible/base/ARIAMap.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_aria_ARIAMap_h_ +#define mozilla_a11y_aria_ARIAMap_h_ + +#include "ARIAStateMap.h" +#include "mozilla/a11y/AccTypes.h" +#include "mozilla/a11y/Role.h" + +#include "nsIAtom.h" +#include "nsIContent.h" + +class nsINode; + +//////////////////////////////////////////////////////////////////////////////// +// Value constants + +/** + * Used to define if role requires to expose Value interface. + */ +enum EValueRule +{ + /** + * Value interface isn't exposed. + */ + eNoValue, + + /** + * Value interface is implemented, supports value, min and max from + * aria-valuenow, aria-valuemin and aria-valuemax. + */ + eHasValueMinMax +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Action constants + +/** + * Used to define if the role requires to expose action. + */ +enum EActionRule +{ + eNoAction, + eActivateAction, + eClickAction, + ePressAction, + eCheckUncheckAction, + eExpandAction, + eJumpAction, + eOpenCloseAction, + eSelectAction, + eSortAction, + eSwitchAction +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Live region constants + +/** + * Used to define if role exposes default value of aria-live attribute. + */ +enum ELiveAttrRule +{ + eNoLiveAttr, + eOffLiveAttr, + ePoliteLiveAttr +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Role constants + +/** + * ARIA role overrides role from native markup. + */ +const bool kUseMapRole = true; + +/** + * ARIA role doesn't override the role from native markup. + */ +const bool kUseNativeRole = false; + + +//////////////////////////////////////////////////////////////////////////////// +// ARIA attribute characteristic masks + +/** + * This mask indicates the attribute should not be exposed as an object + * attribute via the catch-all logic in Accessible::Attributes(). + * This means it either isn't mean't to be exposed as an object attribute, or + * that it should, but is already handled in other code. + */ +const uint8_t ATTR_BYPASSOBJ = 0x1 << 0; +const uint8_t ATTR_BYPASSOBJ_IF_FALSE = 0x1 << 1; + +/** + * This mask indicates the attribute is expected to have an NMTOKEN or bool value. + * (See for example usage in Accessible::Attributes()) + */ +const uint8_t ATTR_VALTOKEN = 0x1 << 2; + +/** + * Indicate the attribute is global state or property (refer to + * http://www.w3.org/TR/wai-aria/states_and_properties#global_states). + */ +const uint8_t ATTR_GLOBAL = 0x1 << 3; + +//////////////////////////////////////////////////////////////////////////////// +// State map entry + +/** + * Used in nsRoleMapEntry.state if no nsIAccessibleStates are automatic for + * a given role. + */ +#define kNoReqStates 0 + +//////////////////////////////////////////////////////////////////////////////// +// Role map entry + +/** + * For each ARIA role, this maps the nsIAccessible information. + */ +struct nsRoleMapEntry +{ + /** + * Return true if matches to the given ARIA role. + */ + bool Is(nsIAtom* aARIARole) const + { return *roleAtom == aARIARole; } + + /** + * Return true if ARIA role has the given accessible type. + */ + bool IsOfType(mozilla::a11y::AccGenericType aType) const + { return accTypes & aType; } + + /** + * Return ARIA role. + */ + const nsDependentAtomString ARIARoleString() const + { return nsDependentAtomString(*roleAtom); } + + // ARIA role: string representation such as "button" + nsIAtom** roleAtom; + + // Role mapping rule: maps to enum Role + mozilla::a11y::role role; + + // Role rule: whether to use mapped role or native semantics + bool roleRule; + + // Value mapping rule: how to compute accessible value + EValueRule valueRule; + + // Action mapping rule, how to expose accessible action + EActionRule actionRule; + + // 'live' and 'container-live' object attributes mapping rule: how to expose + // these object attributes if ARIA 'live' attribute is missed. + ELiveAttrRule liveAttRule; + + // Accessible types this role belongs to. + uint32_t accTypes; + + // Automatic state mapping rule: always include in states + uint64_t state; // or kNoReqStates if no default state for this role + + // ARIA properties supported for this role (in other words, the aria-foo + // attribute to accessible states mapping rules). + // Currently you cannot have unlimited mappings, because + // a variable sized array would not allow the use of + // C++'s struct initialization feature. + mozilla::a11y::aria::EStateRule attributeMap1; + mozilla::a11y::aria::EStateRule attributeMap2; + mozilla::a11y::aria::EStateRule attributeMap3; + mozilla::a11y::aria::EStateRule attributeMap4; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// ARIA map + +/** + * These provide the mappings for WAI-ARIA roles, states and properties using + * the structs defined in this file and ARIAStateMap files. + */ +namespace mozilla { +namespace a11y { +namespace aria { + +/** + * Empty role map entry. Used by accessibility service to create an accessible + * if the accessible can't use role of used accessible class. For example, + * it is used for table cells that aren't contained by table. + */ +extern nsRoleMapEntry gEmptyRoleMap; + +/** + * Constants for the role map entry index to indicate that the role map entry + * isn't in sWAIRoleMaps, but rather is a special entry: nullptr, + * gEmptyRoleMap, and sLandmarkRoleMap + */ +const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2; +const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1; +const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX; + +/** + * Get the role map entry for a given DOM node. This will use the first + * ARIA role if the role attribute provides a space delimited list of roles. + * + * @param aEl [in] the DOM node to get the role map entry for + * @return a pointer to the role map entry for the ARIA role, or nullptr + * if none + */ +const nsRoleMapEntry* GetRoleMap(dom::Element* aEl); + +/** + * Get the role map entry pointer's index for a given DOM node. This will use + * the first ARIA role if the role attribute provides a space delimited list of + * roles. + * + * @param aEl [in] the DOM node to get the role map entry for + * @return the index of the pointer to the role map entry for the ARIA + * role, or NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetRoleMapIndex(dom::Element* aEl); + +/** + * Get the role map entry pointer for a given role map entry index. + * + * @param aRoleMapIndex [in] the role map index to get the role map entry + * pointer for + * @return a pointer to the role map entry for the ARIA role, + * or nullptr, if none + */ +const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex); + +/** + * Get the role map entry index for a given role map entry pointer. If the role + * map entry is within sWAIRoleMaps, return the index within that array, + * otherwise return one of the special index constants listed above. + * + * @param aRoleMap [in] the role map entry pointer to get the index for + * @return the index of the pointer to the role map entry, or + * NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap); + +/** + * Return accessible state from ARIA universal states applied to the given + * element. + */ +uint64_t UniversalStatesFor(mozilla::dom::Element* aElement); + +/** + * Get the ARIA attribute characteristics for a given ARIA attribute. + * + * @param aAtom ARIA attribute + * @return A bitflag representing the attribute characteristics + * (see above for possible bit masks, prefixed "ATTR_") + */ +uint8_t AttrCharacteristicsFor(nsIAtom* aAtom); + +/** + * Return true if the element has defined aria-hidden. + */ +bool HasDefinedARIAHidden(nsIContent* aContent); + + /** + * Represents a simple enumerator for iterating through ARIA attributes + * exposed as object attributes on a given accessible. + */ +class AttrIterator +{ +public: + explicit AttrIterator(nsIContent* aContent) : + mContent(aContent), mAttrIdx(0) + { + mAttrCount = mContent->GetAttrCount(); + } + + bool Next(nsAString& aAttrName, nsAString& aAttrValue); + +private: + AttrIterator() = delete; + AttrIterator(const AttrIterator&) = delete; + AttrIterator& operator= (const AttrIterator&) = delete; + + nsIContent* mContent; + uint32_t mAttrIdx; + uint32_t mAttrCount; +}; + +} // namespace aria +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp new file mode 100644 index 000000000..f121835d1 --- /dev/null +++ b/accessible/base/ARIAStateMap.cpp @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ARIAMap.h" +#include "nsAccUtils.h" +#include "States.h" + +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; +using namespace mozilla::a11y::aria; + +/** + * Used to store state map rule data for ARIA attribute of enum type. + */ +struct EnumTypeData +{ + // ARIA attribute name. + nsIAtom* const mAttrName; + + // States if the attribute value is matched to the enum value. Used as + // nsIContent::AttrValuesArray, last item must be nullptr. + nsIAtom* const* const mValues[4]; + + // States applied if corresponding enum values are matched. + const uint64_t mStates[3]; + + // States to clear in case of match. + const uint64_t mClearState; +}; + +enum ETokenType +{ + eBoolType = 0, + eMixedType = 1, // can take 'mixed' value + eDefinedIfAbsent = 2 // permanent and false state are applied if absent +}; + +/** + * Used to store state map rule data for ARIA attribute of token type (including + * mixed value). + */ +struct TokenTypeData +{ + TokenTypeData(nsIAtom* aAttrName, uint32_t aType, + uint64_t aPermanentState, + uint64_t aTrueState, + uint64_t aFalseState = 0) : + mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState), + mTrueState(aTrueState), mFalseState(aFalseState) + { } + + // ARIA attribute name. + nsIAtom* const mAttrName; + + // Type. + const uint32_t mType; + + // State applied if the attribute is defined or mType doesn't have + // eDefinedIfAbsent flag set. + const uint64_t mPermanentState; + + // States applied if the attribute value is true/false. + const uint64_t mTrueState; + const uint64_t mFalseState; +}; + +/** + * Map enum type attribute value to accessible state. + */ +static void MapEnumType(dom::Element* aElement, uint64_t* aState, + const EnumTypeData& aData); + +/** + * Map token type attribute value to states. + */ +static void MapTokenType(dom::Element* aContent, uint64_t* aState, + const TokenTypeData& aData); + +bool +aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState) +{ + switch (aRule) { + case eARIAAutoComplete: + { + static const EnumTypeData data = { + nsGkAtoms::aria_autocomplete, + { &nsGkAtoms::inlinevalue, + &nsGkAtoms::list, + &nsGkAtoms::both, nullptr }, + { states::SUPPORTS_AUTOCOMPLETION, + states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION, + states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION }, 0 + }; + + MapEnumType(aElement, aState, data); + return true; + } + + case eARIABusy: + { + static const EnumTypeData data = { + nsGkAtoms::aria_busy, + { &nsGkAtoms::_true, + &nsGkAtoms::error, nullptr }, + { states::BUSY, + states::INVALID }, 0 + }; + + MapEnumType(aElement, aState, data); + return true; + } + + case eARIACheckableBool: + { + static const TokenTypeData data( + nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent, + states::CHECKABLE, states::CHECKED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIACheckableMixed: + { + static const TokenTypeData data( + nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent, + states::CHECKABLE, states::CHECKED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIACheckedMixed: + { + static const TokenTypeData data( + nsGkAtoms::aria_checked, eMixedType, + states::CHECKABLE, states::CHECKED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIADisabled: + { + static const TokenTypeData data( + nsGkAtoms::aria_disabled, eBoolType, + 0, states::UNAVAILABLE); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAExpanded: + { + static const TokenTypeData data( + nsGkAtoms::aria_expanded, eBoolType, + 0, states::EXPANDED, states::COLLAPSED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAHasPopup: + { + static const TokenTypeData data( + nsGkAtoms::aria_haspopup, eBoolType, + 0, states::HASPOPUP); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAInvalid: + { + static const TokenTypeData data( + nsGkAtoms::aria_invalid, eBoolType, + 0, states::INVALID); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAModal: + { + static const TokenTypeData data( + nsGkAtoms::aria_modal, eBoolType, + 0, states::MODAL); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAMultiline: + { + static const TokenTypeData data( + nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent, + 0, states::MULTI_LINE, states::SINGLE_LINE); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAMultiSelectable: + { + static const TokenTypeData data( + nsGkAtoms::aria_multiselectable, eBoolType, + 0, states::MULTISELECTABLE | states::EXTSELECTABLE); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAOrientation: + { + static const EnumTypeData data = { + nsGkAtoms::aria_orientation, + { &nsGkAtoms::horizontal, + &nsGkAtoms::vertical, nullptr }, + { states::HORIZONTAL, + states::VERTICAL }, + states::HORIZONTAL | states::VERTICAL + }; + + MapEnumType(aElement, aState, data); + return true; + } + + case eARIAPressed: + { + static const TokenTypeData data( + nsGkAtoms::aria_pressed, eMixedType, + 0, states::PRESSED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAReadonly: + { + static const TokenTypeData data( + nsGkAtoms::aria_readonly, eBoolType, + 0, states::READONLY); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAReadonlyOrEditable: + { + static const TokenTypeData data( + nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent, + 0, states::READONLY, states::EDITABLE); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIAReadonlyOrEditableIfDefined: + { + static const TokenTypeData data( + nsGkAtoms::aria_readonly, eBoolType, + 0, states::READONLY, states::EDITABLE); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIARequired: + { + static const TokenTypeData data( + nsGkAtoms::aria_required, eBoolType, + 0, states::REQUIRED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIASelectable: + { + static const TokenTypeData data( + nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent, + states::SELECTABLE, states::SELECTED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eARIASelectableIfDefined: + { + static const TokenTypeData data( + nsGkAtoms::aria_selected, eBoolType, + states::SELECTABLE, states::SELECTED); + + MapTokenType(aElement, aState, data); + return true; + } + + case eReadonlyUntilEditable: + { + if (!(*aState & states::EDITABLE)) + *aState |= states::READONLY; + + return true; + } + + case eIndeterminateIfNoValue: + { + if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) && + !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext)) + *aState |= states::MIXED; + + return true; + } + + case eFocusableUntilDisabled: + { + if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) || + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, + nsGkAtoms::_false, eCaseMatters)) + *aState |= states::FOCUSABLE; + + return true; + } + + default: + return false; + } +} + +static void +MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData) +{ + switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName, + aData.mValues, eCaseMatters)) { + case 0: + *aState = (*aState & ~aData.mClearState) | aData.mStates[0]; + return; + case 1: + *aState = (*aState & ~aData.mClearState) | aData.mStates[1]; + return; + case 2: + *aState = (*aState & ~aData.mClearState) | aData.mStates[2]; + return; + } +} + +static void +MapTokenType(dom::Element* aElement, uint64_t* aState, + const TokenTypeData& aData) +{ + if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) { + if ((aData.mType & eMixedType) && + aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, + nsGkAtoms::mixed, eCaseMatters)) { + *aState |= aData.mPermanentState | states::MIXED; + return; + } + + if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, + nsGkAtoms::_false, eCaseMatters)) { + *aState |= aData.mPermanentState | aData.mFalseState; + return; + } + + *aState |= aData.mPermanentState | aData.mTrueState; + return; + } + + if (aData.mType & eDefinedIfAbsent) + *aState |= aData.mPermanentState | aData.mFalseState; +} diff --git a/accessible/base/ARIAStateMap.h b/accessible/base/ARIAStateMap.h new file mode 100644 index 000000000..41626fcaf --- /dev/null +++ b/accessible/base/ARIAStateMap.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _mozilla_a11y_aria_ARIAStateMap_h_ +#define _mozilla_a11y_aria_ARIAStateMap_h_ + +#include + +namespace mozilla { + +namespace dom { +class Element; +} + +namespace a11y { +namespace aria { + +/** + * List of the ARIA state mapping rules. + */ +enum EStateRule +{ + eARIANone, + eARIAAutoComplete, + eARIABusy, + eARIACheckableBool, + eARIACheckableMixed, + eARIACheckedMixed, + eARIADisabled, + eARIAExpanded, + eARIAHasPopup, + eARIAInvalid, + eARIAModal, + eARIAMultiline, + eARIAMultiSelectable, + eARIAOrientation, + eARIAPressed, + eARIAReadonly, + eARIAReadonlyOrEditable, + eARIAReadonlyOrEditableIfDefined, + eARIARequired, + eARIASelectable, + eARIASelectableIfDefined, + eReadonlyUntilEditable, + eIndeterminateIfNoValue, + eFocusableUntilDisabled +}; + +/** + * Expose the accessible states for the given element accordingly to state + * mapping rule. + * + * @param aRule [in] state mapping rule ID + * @param aElement [in] node of the accessible + * @param aState [in/out] accessible states + * @return true if state map rule ID is valid + */ +bool MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState); + +} // namespace aria +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp new file mode 100644 index 000000000..a27ceacb9 --- /dev/null +++ b/accessible/base/AccEvent.cpp @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccEvent.h" + +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "xpcAccEvents.h" +#include "States.h" +#include "xpcAccessibleDocument.h" + +#include "mozilla/EventStateManager.h" +#include "mozilla/dom/Selection.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +static_assert(static_cast(eNoUserInput) == false && + static_cast(eFromUserInput) == true, + "EIsFromUserInput cannot be casted to bool"); + +//////////////////////////////////////////////////////////////////////////////// +// AccEvent +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// AccEvent constructors + +AccEvent::AccEvent(uint32_t aEventType, Accessible* aAccessible, + EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) : + mEventType(aEventType), mEventRule(aEventRule), mAccessible(aAccessible) +{ + if (aIsFromUserInput == eAutoDetect) + mIsFromUserInput = EventStateManager::IsHandlingUserInput(); + else + mIsFromUserInput = aIsFromUserInput == eFromUserInput ? true : false; +} + +//////////////////////////////////////////////////////////////////////////////// +// AccEvent cycle collection + +NS_IMPL_CYCLE_COLLECTION_CLASS(AccEvent) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AccEvent) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible) + if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) { + tmEvent->SetNextEvent(nullptr); + tmEvent->SetPrevEvent(nullptr); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AccEvent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessible) + if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) { + CycleCollectionNoteChild(cb, tmEvent->NextEvent(), "mNext"); + CycleCollectionNoteChild(cb, tmEvent->PrevEvent(), "mPrevEvent"); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release) + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// AccTextChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +// Note: we pass in eAllowDupes to the base class because we don't support text +// events coalescence. We fire delayed text change events in DocAccessible but +// we continue to base the event off the accessible object rather than just the +// node. This means we won't try to create an accessible based on the node when +// we are ready to fire the event and so we will no longer assert at that point +// if the node was removed from the document. Either way, the AT won't work with +// a defunct accessible so the behaviour should be equivalent. +AccTextChangeEvent:: + AccTextChangeEvent(Accessible* aAccessible, int32_t aStart, + const nsAString& aModifiedText, bool aIsInserted, + EIsFromUserInput aIsFromUserInput) + : AccEvent(aIsInserted ? + static_cast(nsIAccessibleEvent::EVENT_TEXT_INSERTED) : + static_cast(nsIAccessibleEvent::EVENT_TEXT_REMOVED), + aAccessible, aIsFromUserInput, eAllowDupes) + , mStart(aStart) + , mIsInserted(aIsInserted) + , mModifiedText(aModifiedText) +{ + // XXX We should use IsFromUserInput here, but that isn't always correct + // when the text change isn't related to content insertion or removal. + mIsFromUserInput = mAccessible->State() & + (states::FOCUSED | states::EDITABLE); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccHideEvent +//////////////////////////////////////////////////////////////////////////////// + +AccHideEvent:: + AccHideEvent(Accessible* aTarget, bool aNeedsShutdown) : + AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget), + mNeedsShutdown(aNeedsShutdown) +{ + mNextSibling = mAccessible->NextSibling(); + mPrevSibling = mAccessible->PrevSibling(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// AccShowEvent +//////////////////////////////////////////////////////////////////////////////// + +AccShowEvent:: + AccShowEvent(Accessible* aTarget) : + AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget) +{ + int32_t idx = aTarget->IndexInParent(); + MOZ_ASSERT(idx >= 0); + mInsertionIndex = idx; +} + + +//////////////////////////////////////////////////////////////////////////////// +// AccTextSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, + int32_t aReason) : + AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget, + eAutoDetect, eCoalesceTextSelChange), + mSel(aSelection), mReason(aReason) {} + +AccTextSelChangeEvent::~AccTextSelChangeEvent() { } + +bool +AccTextSelChangeEvent::IsCaretMoveOnly() const +{ + return mSel->RangeCount() == 1 && mSel->IsCollapsed() && + ((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON | + nsISelectionListener::COLLAPSETOEND_REASON)) == 0); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccSelChangeEvent:: + AccSelChangeEvent(Accessible* aWidget, Accessible* aItem, + SelChangeType aSelChangeType) : + AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange), + mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType), + mPreceedingCount(0), mPackedEvent(nullptr) +{ + if (aSelChangeType == eSelectionAdd) { + if (mWidget->GetSelectedItem(1)) + mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; + else + mEventType = nsIAccessibleEvent::EVENT_SELECTION; + } else { + mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// AccTableChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccTableChangeEvent:: + AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType, + int32_t aRowOrColIndex, int32_t aNumRowsOrCols) : + AccEvent(aEventType, aAccessible), + mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +// AccVCChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccVCChangeEvent:: + AccVCChangeEvent(Accessible* aAccessible, + Accessible* aOldAccessible, + int32_t aOldStart, int32_t aOldEnd, + int16_t aReason, EIsFromUserInput aIsFromUserInput) : + AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible, + aIsFromUserInput), + mOldAccessible(aOldAccessible), mOldStart(aOldStart), mOldEnd(aOldEnd), + mReason(aReason) +{ +} + +already_AddRefed +a11y::MakeXPCEvent(AccEvent* aEvent) +{ + DocAccessible* doc = aEvent->Document(); + Accessible* acc = aEvent->GetAccessible(); + nsINode* node = acc->GetNode(); + nsIDOMNode* domNode = node ? node->AsDOMNode() : nullptr; + bool fromUser = aEvent->IsFromUserInput(); + uint32_t type = aEvent->GetEventType(); + uint32_t eventGroup = aEvent->GetEventGroups(); + nsCOMPtr xpEvent; + + if (eventGroup & (1 << AccEvent::eStateChangeEvent)) { + AccStateChangeEvent* sc = downcast_accEvent(aEvent); + bool extra = false; + uint32_t state = nsAccUtils::To32States(sc->GetState(), &extra); + xpEvent = new xpcAccStateChangeEvent(type, ToXPC(acc), ToXPCDocument(doc), + domNode, fromUser, + state, extra, sc->IsStateEnabled()); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eTextChangeEvent)) { + AccTextChangeEvent* tc = downcast_accEvent(aEvent); + nsString text; + tc->GetModifiedText(text); + xpEvent = new xpcAccTextChangeEvent(type, ToXPC(acc), ToXPCDocument(doc), + domNode, fromUser, + tc->GetStartOffset(), tc->GetLength(), + tc->IsTextInserted(), text); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eHideEvent)) { + AccHideEvent* hideEvent = downcast_accEvent(aEvent); + xpEvent = new xpcAccHideEvent(type, ToXPC(acc), ToXPCDocument(doc), + domNode, fromUser, + ToXPC(hideEvent->TargetParent()), + ToXPC(hideEvent->TargetNextSibling()), + ToXPC(hideEvent->TargetPrevSibling())); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eCaretMoveEvent)) { + AccCaretMoveEvent* cm = downcast_accEvent(aEvent); + xpEvent = new xpcAccCaretMoveEvent(type, ToXPC(acc), ToXPCDocument(doc), + domNode, fromUser, + cm->GetCaretOffset()); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) { + AccVCChangeEvent* vcc = downcast_accEvent(aEvent); + xpEvent = new xpcAccVirtualCursorChangeEvent(type, + ToXPC(acc), ToXPCDocument(doc), + domNode, fromUser, + ToXPC(vcc->OldAccessible()), + vcc->OldStartOffset(), + vcc->OldEndOffset(), + vcc->Reason()); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eObjectAttrChangedEvent)) { + AccObjectAttrChangedEvent* oac = downcast_accEvent(aEvent); + xpEvent = new xpcAccObjectAttributeChangedEvent(type, + ToXPC(acc), + ToXPCDocument(doc), domNode, + fromUser, + oac->GetAttribute()); + return xpEvent.forget(); + } + + xpEvent = new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), domNode, fromUser); + return xpEvent.forget(); + } diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h new file mode 100644 index 000000000..416224278 --- /dev/null +++ b/accessible/base/AccEvent.h @@ -0,0 +1,570 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _AccEvent_H_ +#define _AccEvent_H_ + +#include "nsIAccessibleEvent.h" + +#include "mozilla/a11y/Accessible.h" + +class nsEventShell; +namespace mozilla { + +namespace dom { +class Selection; +} + +namespace a11y { + +class DocAccessible; + +// Constants used to point whether the event is from user input. +enum EIsFromUserInput +{ + // eNoUserInput: event is not from user input + eNoUserInput = 0, + // eFromUserInput: event is from user input + eFromUserInput = 1, + // eAutoDetect: the value should be obtained from event state manager + eAutoDetect = -1 +}; + +/** + * Generic accessible event. + */ +class AccEvent +{ +public: + + // Rule for accessible events. + // The rule will be applied when flushing pending events. + enum EEventRule { + // eAllowDupes : More than one event of the same type is allowed. + // This event will always be emitted. This flag is used for events that + // don't support coalescence. + eAllowDupes, + + // eCoalesceReorder : For reorder events from the same subtree or the same + // node, only the umbrella event on the ancestor will be emitted. + eCoalesceReorder, + + // eCoalesceOfSameType : For events of the same type, only the newest event + // will be processed. + eCoalesceOfSameType, + + // eCoalesceSelectionChange: coalescence of selection change events. + eCoalesceSelectionChange, + + // eCoalesceStateChange: coalesce state change events. + eCoalesceStateChange, + + // eCoalesceTextSelChange: coalescence of text selection change events. + eCoalesceTextSelChange, + + // eRemoveDupes : For repeat events, only the newest event in queue + // will be emitted. + eRemoveDupes, + + // eDoNotEmit : This event is confirmed as a duplicate, do not emit it. + eDoNotEmit + }; + + // Initialize with an accessible. + AccEvent(uint32_t aEventType, Accessible* aAccessible, + EIsFromUserInput aIsFromUserInput = eAutoDetect, + EEventRule aEventRule = eRemoveDupes); + + // AccEvent + uint32_t GetEventType() const { return mEventType; } + EEventRule GetEventRule() const { return mEventRule; } + bool IsFromUserInput() const { return mIsFromUserInput; } + EIsFromUserInput FromUserInput() const + { return static_cast(mIsFromUserInput); } + + Accessible* GetAccessible() const { return mAccessible; } + DocAccessible* Document() const { return mAccessible->Document(); } + + /** + * Down casting. + */ + enum EventGroup { + eGenericEvent, + eStateChangeEvent, + eTextChangeEvent, + eTreeMutationEvent, + eMutationEvent, + eReorderEvent, + eHideEvent, + eShowEvent, + eCaretMoveEvent, + eTextSelChangeEvent, + eSelectionChangeEvent, + eTableChangeEvent, + eVirtualCursorChangeEvent, + eObjectAttrChangedEvent + }; + + static const EventGroup kEventGroup = eGenericEvent; + virtual unsigned int GetEventGroups() const + { + return 1U << eGenericEvent; + } + + /** + * Reference counting and cycle collection. + */ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent) + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent) + +protected: + virtual ~AccEvent() {} + + bool mIsFromUserInput; + uint32_t mEventType; + EEventRule mEventRule; + RefPtr mAccessible; + + friend class EventQueue; + friend class EventTree; + friend class ::nsEventShell; + friend class NotificationController; +}; + + +/** + * Accessible state change event. + */ +class AccStateChangeEvent: public AccEvent +{ +public: + AccStateChangeEvent(Accessible* aAccessible, uint64_t aState, + bool aIsEnabled, + EIsFromUserInput aIsFromUserInput = eAutoDetect) : + AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, + aIsFromUserInput, eCoalesceStateChange), + mState(aState), mIsEnabled(aIsEnabled) { } + + AccStateChangeEvent(Accessible* aAccessible, uint64_t aState) : + AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, + eAutoDetect, eCoalesceStateChange), mState(aState) + { mIsEnabled = (mAccessible->State() & mState) != 0; } + + // AccEvent + static const EventGroup kEventGroup = eStateChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eStateChangeEvent); + } + + // AccStateChangeEvent + uint64_t GetState() const { return mState; } + bool IsStateEnabled() const { return mIsEnabled; } + +private: + uint64_t mState; + bool mIsEnabled; + + friend class EventQueue; +}; + + +/** + * Accessible text change event. + */ +class AccTextChangeEvent: public AccEvent +{ +public: + AccTextChangeEvent(Accessible* aAccessible, int32_t aStart, + const nsAString& aModifiedText, bool aIsInserted, + EIsFromUserInput aIsFromUserInput = eAutoDetect); + + // AccEvent + static const EventGroup kEventGroup = eTextChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eTextChangeEvent); + } + + // AccTextChangeEvent + int32_t GetStartOffset() const { return mStart; } + uint32_t GetLength() const { return mModifiedText.Length(); } + bool IsTextInserted() const { return mIsInserted; } + void GetModifiedText(nsAString& aModifiedText) + { aModifiedText = mModifiedText; } + const nsString& ModifiedText() const { return mModifiedText; } + +private: + int32_t mStart; + bool mIsInserted; + nsString mModifiedText; + + friend class EventTree; + friend class NotificationController; +}; + +/** + * A base class for events related to tree mutation, either an AccMutation + * event, or an AccReorderEvent. + */ +class AccTreeMutationEvent : public AccEvent +{ +public: + AccTreeMutationEvent(uint32_t aEventType, Accessible* aTarget) : + AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder), mGeneration(0) {} + + // Event + static const EventGroup kEventGroup = eTreeMutationEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent); + } + + void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; } + void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; } + AccTreeMutationEvent* NextEvent() const { return mNextEvent; } + AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; } + + /** + * A sequence number to know when this event was fired. + */ + uint32_t EventGeneration() const { return mGeneration; } + void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; } + +private: + RefPtr mNextEvent; + RefPtr mPrevEvent; + uint32_t mGeneration; +}; + +/** + * Base class for show and hide accessible events. + */ +class AccMutationEvent: public AccTreeMutationEvent +{ +public: + AccMutationEvent(uint32_t aEventType, Accessible* aTarget) : + AccTreeMutationEvent(aEventType, aTarget) + { + // Don't coalesce these since they are coalesced by reorder event. Coalesce + // contained text change events. + mParent = mAccessible->Parent(); + } + virtual ~AccMutationEvent() { } + + // Event + static const EventGroup kEventGroup = eMutationEvent; + virtual unsigned int GetEventGroups() const override + { + return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent); + } + + // MutationEvent + bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; } + bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; } + + Accessible* Parent() const { return mParent; } + +protected: + nsCOMPtr mNode; + RefPtr mParent; + RefPtr mTextChangeEvent; + + friend class EventTree; + friend class NotificationController; +}; + + +/** + * Accessible hide event. + */ +class AccHideEvent: public AccMutationEvent +{ +public: + explicit AccHideEvent(Accessible* aTarget, bool aNeedsShutdown = true); + + // Event + static const EventGroup kEventGroup = eHideEvent; + virtual unsigned int GetEventGroups() const override + { + return AccMutationEvent::GetEventGroups() | (1U << eHideEvent); + } + + // AccHideEvent + Accessible* TargetParent() const { return mParent; } + Accessible* TargetNextSibling() const { return mNextSibling; } + Accessible* TargetPrevSibling() const { return mPrevSibling; } + bool NeedsShutdown() const { return mNeedsShutdown; } + +protected: + bool mNeedsShutdown; + RefPtr mNextSibling; + RefPtr mPrevSibling; + + friend class EventTree; + friend class NotificationController; +}; + + +/** + * Accessible show event. + */ +class AccShowEvent: public AccMutationEvent +{ +public: + explicit AccShowEvent(Accessible* aTarget); + + // Event + static const EventGroup kEventGroup = eShowEvent; + virtual unsigned int GetEventGroups() const override + { + return AccMutationEvent::GetEventGroups() | (1U << eShowEvent); + } + + uint32_t InsertionIndex() const { return mInsertionIndex; } + +private: + nsTArray> mPrecedingEvents; + uint32_t mInsertionIndex; + + friend class EventTree; +}; + + +/** + * Class for reorder accessible event. Takes care about + */ +class AccReorderEvent : public AccTreeMutationEvent +{ +public: + explicit AccReorderEvent(Accessible* aTarget) : + AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) { } + virtual ~AccReorderEvent() { } + + // Event + static const EventGroup kEventGroup = eReorderEvent; + virtual unsigned int GetEventGroups() const override + { + return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent); + } +}; + + +/** + * Accessible caret move event. + */ +class AccCaretMoveEvent: public AccEvent +{ +public: + AccCaretMoveEvent(Accessible* aAccessible, int32_t aCaretOffset, + EIsFromUserInput aIsFromUserInput = eAutoDetect) : + AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible, + aIsFromUserInput), + mCaretOffset(aCaretOffset) { } + virtual ~AccCaretMoveEvent() { } + + // AccEvent + static const EventGroup kEventGroup = eCaretMoveEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eCaretMoveEvent); + } + + // AccCaretMoveEvent + int32_t GetCaretOffset() const { return mCaretOffset; } + +private: + int32_t mCaretOffset; +}; + + +/** + * Accessible text selection change event. + */ +class AccTextSelChangeEvent : public AccEvent +{ +public: + AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, + int32_t aReason); + virtual ~AccTextSelChangeEvent(); + + // AccEvent + static const EventGroup kEventGroup = eTextSelChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent); + } + + // AccTextSelChangeEvent + + /** + * Return true if the text selection change wasn't caused by pure caret move. + */ + bool IsCaretMoveOnly() const; + +private: + RefPtr mSel; + int32_t mReason; + + friend class EventQueue; + friend class SelectionManager; +}; + + +/** + * Accessible widget selection change event. + */ +class AccSelChangeEvent : public AccEvent +{ +public: + enum SelChangeType { + eSelectionAdd, + eSelectionRemove + }; + + AccSelChangeEvent(Accessible* aWidget, Accessible* aItem, + SelChangeType aSelChangeType); + + virtual ~AccSelChangeEvent() { } + + // AccEvent + static const EventGroup kEventGroup = eSelectionChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent); + } + + // AccSelChangeEvent + Accessible* Widget() const { return mWidget; } + +private: + RefPtr mWidget; + RefPtr mItem; + SelChangeType mSelChangeType; + uint32_t mPreceedingCount; + AccSelChangeEvent* mPackedEvent; + + friend class EventQueue; +}; + + +/** + * Accessible table change event. + */ +class AccTableChangeEvent : public AccEvent +{ +public: + AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType, + int32_t aRowOrColIndex, int32_t aNumRowsOrCols); + + // AccEvent + static const EventGroup kEventGroup = eTableChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eTableChangeEvent); + } + + // AccTableChangeEvent + uint32_t GetIndex() const { return mRowOrColIndex; } + uint32_t GetCount() const { return mNumRowsOrCols; } + +private: + uint32_t mRowOrColIndex; // the start row/column after which the rows are inserted/deleted. + uint32_t mNumRowsOrCols; // the number of inserted/deleted rows/columns +}; + +/** + * Accessible virtual cursor change event. + */ +class AccVCChangeEvent : public AccEvent +{ +public: + AccVCChangeEvent(Accessible* aAccessible, + Accessible* aOldAccessible, + int32_t aOldStart, int32_t aOldEnd, + int16_t aReason, + EIsFromUserInput aIsFromUserInput = eFromUserInput); + + virtual ~AccVCChangeEvent() { } + + // AccEvent + static const EventGroup kEventGroup = eVirtualCursorChangeEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eVirtualCursorChangeEvent); + } + + // AccTableChangeEvent + Accessible* OldAccessible() const { return mOldAccessible; } + int32_t OldStartOffset() const { return mOldStart; } + int32_t OldEndOffset() const { return mOldEnd; } + int32_t Reason() const { return mReason; } + +private: + RefPtr mOldAccessible; + int32_t mOldStart; + int32_t mOldEnd; + int16_t mReason; +}; + +/** + * Accessible object attribute changed event. + */ +class AccObjectAttrChangedEvent: public AccEvent +{ +public: + AccObjectAttrChangedEvent(Accessible* aAccessible, nsIAtom* aAttribute) : + AccEvent(::nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, aAccessible), + mAttribute(aAttribute) { } + + // AccEvent + static const EventGroup kEventGroup = eObjectAttrChangedEvent; + virtual unsigned int GetEventGroups() const override + { + return AccEvent::GetEventGroups() | (1U << eObjectAttrChangedEvent); + } + + // AccObjectAttrChangedEvent + nsIAtom* GetAttribute() const { return mAttribute; } + +private: + nsCOMPtr mAttribute; + + virtual ~AccObjectAttrChangedEvent() { } +}; + +/** + * Downcast the generic accessible event object to derived type. + */ +class downcast_accEvent +{ +public: + explicit downcast_accEvent(AccEvent* e) : mRawPtr(e) { } + + template + operator Destination*() { + if (!mRawPtr) + return nullptr; + + return mRawPtr->GetEventGroups() & (1U << Destination::kEventGroup) ? + static_cast(mRawPtr) : nullptr; + } + +private: + AccEvent* mRawPtr; +}; + +/** + * Return a new xpcom accessible event for the given internal one. + */ +already_AddRefed +MakeXPCEvent(AccEvent* aEvent); + +} // namespace a11y +} // namespace mozilla + +#endif + diff --git a/accessible/base/AccGroupInfo.cpp b/accessible/base/AccGroupInfo.cpp new file mode 100644 index 000000000..bef707887 --- /dev/null +++ b/accessible/base/AccGroupInfo.cpp @@ -0,0 +1,227 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccGroupInfo.h" +#include "nsAccUtils.h" + +#include "Role.h" +#include "States.h" + +using namespace mozilla::a11y; + +AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) : + mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole) +{ + MOZ_COUNT_CTOR(AccGroupInfo); + Update(); +} + +void +AccGroupInfo::Update() +{ + Accessible* parent = mItem->Parent(); + if (!parent) + return; + + int32_t indexInParent = mItem->IndexInParent(); + uint32_t siblingCount = parent->ChildCount(); + if (indexInParent == -1 || + indexInParent >= static_cast(siblingCount)) { + NS_ERROR("Wrong index in parent! Tree invalidation problem."); + return; + } + + int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem); + + // Compute position in set. + mPosInSet = 1; + for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) { + Accessible* sibling = parent->GetChildAt(idx); + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) + break; + + // If sibling is not visible and hasn't the same base role. + if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) + continue; + + // Check if it's hierarchical flatten structure, i.e. if the sibling + // level is lesser than this one then group is ended, if the sibling level + // is greater than this one then the group is split by some child elements + // (group will be continued). + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) { + mParent = sibling; + break; + } + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the previous item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mBits.groupInfo) { + mPosInSet += sibling->mBits.groupInfo->mPosInSet; + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mPosInSet++; + } + + // Compute set size. + mSetSize = mPosInSet; + + for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) { + Accessible* sibling = parent->GetChildAt(idx); + + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) + break; + + // If sibling is visible and has the same base role + if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE) + continue; + + // and check if it's hierarchical flatten structure. + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) + break; + + // Skip subset. + if (siblingLevel > level) + continue; + + // If the next item in the group has calculated group information then + // build group information for this item based on found one. + if (sibling->mBits.groupInfo) { + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mSetSize++; + } + + if (mParent) + return; + + roles::Role parentRole = parent->Role(); + if (ShouldReportRelations(mRole, parentRole)) + mParent = parent; + + // ARIA tree and list can be arranged by using ARIA groups to organize levels. + if (parentRole != roles::GROUPING) + return; + + // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a + // parent. In other words the parent of the tree item will be a group and + // the previous tree item of the group is a conceptual parent of the tree + // item. + if (mRole == roles::OUTLINEITEM) { + Accessible* parentPrevSibling = parent->PrevSibling(); + if (parentPrevSibling && parentPrevSibling->Role() == mRole) { + mParent = parentPrevSibling; + return; + } + } + + // Way #2 for ARIA list and tree: group is a child of an item. In other words + // the parent of the item will be a group and containing item of the group is + // a conceptual parent of the item. + if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { + Accessible* grandParent = parent->Parent(); + if (grandParent && grandParent->Role() == mRole) + mParent = grandParent; + } +} + +Accessible* +AccGroupInfo::FirstItemOf(Accessible* aContainer) +{ + // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a + // group is a parent) or by aria-level. + a11y::role containerRole = aContainer->Role(); + Accessible* item = aContainer->NextSibling(); + if (item) { + if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING) + item = item->FirstChild(); + + if (item) { + AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // ARIA list and tree can be arranged by ARIA groups case #2 (group is + // a child of an item). + item = aContainer->LastChild(); + if (!item) + return nullptr; + + if (item->Role() == roles::GROUPING && + (containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) { + item = item->FirstChild(); + if (item) { + AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // Otherwise, it can be a direct child if the container is a list or tree. + item = aContainer->FirstChild(); + if (ShouldReportRelations(item->Role(), containerRole)) + return item; + + return nullptr; +} + +Accessible* +AccGroupInfo::NextItemTo(Accessible* aItem) +{ + AccGroupInfo* groupInfo = aItem->GetGroupInfo(); + if (!groupInfo) + return nullptr; + + // If the item in middle of the group then search next item in siblings. + if (groupInfo->PosInSet() >= groupInfo->SetSize()) + return nullptr; + + Accessible* parent = aItem->Parent(); + uint32_t childCount = parent->ChildCount(); + for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) { + Accessible* nextItem = parent->GetChildAt(idx); + AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo(); + if (nextGroupInfo && + nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { + return nextItem; + } + } + + NS_NOTREACHED("Item in the middle of the group but there's no next item!"); + return nullptr; +} + +bool +AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) +{ + // We only want to report hierarchy-based node relations for items in tree or + // list form. ARIA level/owns relations are always reported. + if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) + return true; + if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) + return true; + if (aParentRole == roles::LIST && aRole == roles::LISTITEM) + return true; + + return false; +} diff --git a/accessible/base/AccGroupInfo.h b/accessible/base/AccGroupInfo.h new file mode 100644 index 000000000..093258070 --- /dev/null +++ b/accessible/base/AccGroupInfo.h @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AccGroupInfo_h_ +#define AccGroupInfo_h_ + +#include "Accessible-inl.h" + +namespace mozilla { +namespace a11y { + +/** + * Calculate and store group information. + */ +class AccGroupInfo +{ +public: + ~AccGroupInfo() { MOZ_COUNT_DTOR(AccGroupInfo); } + + /** + * Return 1-based position in the group. + */ + uint32_t PosInSet() const { return mPosInSet; } + + /** + * Return a number of items in the group. + */ + uint32_t SetSize() const { return mSetSize; } + + /** + * Return a direct or logical parent of the accessible that this group info is + * created for. + */ + Accessible* ConceptualParent() const { return mParent; } + + /** + * Update group information. + */ + void Update(); + + /** + * Create group info. + */ + static AccGroupInfo* CreateGroupInfo(Accessible* aAccessible) + { + mozilla::a11y::role role = aAccessible->Role(); + if (role != mozilla::a11y::roles::ROW && + role != mozilla::a11y::roles::OUTLINEITEM && + role != mozilla::a11y::roles::OPTION && + role != mozilla::a11y::roles::LISTITEM && + role != mozilla::a11y::roles::MENUITEM && + role != mozilla::a11y::roles::COMBOBOX_OPTION && + role != mozilla::a11y::roles::RICH_OPTION && + role != mozilla::a11y::roles::CHECK_RICH_OPTION && + role != mozilla::a11y::roles::PARENT_MENUITEM && + role != mozilla::a11y::roles::CHECK_MENU_ITEM && + role != mozilla::a11y::roles::RADIO_MENU_ITEM && + role != mozilla::a11y::roles::RADIOBUTTON && + role != mozilla::a11y::roles::PAGETAB) + return nullptr; + + AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role)); + return info; + } + + /** + * Return a first item for the given container. + */ + static Accessible* FirstItemOf(Accessible* aContainer); + + /** + * Return next item of the same group to the given item. + */ + static Accessible* NextItemTo(Accessible* aItem); + +protected: + AccGroupInfo(Accessible* aItem, a11y::role aRole); + +private: + AccGroupInfo() = delete; + AccGroupInfo(const AccGroupInfo&) = delete; + AccGroupInfo& operator =(const AccGroupInfo&) = delete; + + static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole) + { + if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM || + aRole == mozilla::a11y::roles::PARENT_MENUITEM || + aRole == mozilla::a11y::roles::RADIO_MENU_ITEM) + return mozilla::a11y::roles::MENUITEM; + + if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION) + return mozilla::a11y::roles::RICH_OPTION; + + return aRole; + } + + /** + * Return true if the given parent and child roles should have their node + * relations reported. + */ + static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole); + + uint32_t mPosInSet; + uint32_t mSetSize; + Accessible* mParent; + Accessible* mItem; + a11y::role mRole; +}; + +} // namespace mozilla +} // namespace a11y + +#endif diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp new file mode 100644 index 000000000..f6e890c50 --- /dev/null +++ b/accessible/base/AccIterator.cpp @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccIterator.h" + +#include "AccGroupInfo.h" +#ifdef MOZ_XUL +#include "XULTreeAccessible.h" +#endif + +#include "mozilla/dom/HTMLLabelElement.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// AccIterator +//////////////////////////////////////////////////////////////////////////////// + +AccIterator::AccIterator(Accessible* aAccessible, + filters::FilterFuncPtr aFilterFunc) : + mFilterFunc(aFilterFunc) +{ + mState = new IteratorState(aAccessible); +} + +AccIterator::~AccIterator() +{ + while (mState) { + IteratorState *tmp = mState; + mState = tmp->mParentState; + delete tmp; + } +} + +Accessible* +AccIterator::Next() +{ + while (mState) { + Accessible* child = mState->mParent->GetChildAt(mState->mIndex++); + if (!child) { + IteratorState* tmp = mState; + mState = mState->mParentState; + delete tmp; + + continue; + } + + uint32_t result = mFilterFunc(child); + if (result & filters::eMatch) + return child; + + if (!(result & filters::eSkipSubtree)) { + IteratorState* childState = new IteratorState(child, mState); + mState = childState; + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAccIterator::IteratorState + +AccIterator::IteratorState::IteratorState(Accessible* aParent, + IteratorState *mParentState) : + mParent(aParent), mIndex(0), mParentState(mParentState) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +// RelatedAccIterator +//////////////////////////////////////////////////////////////////////////////// + +RelatedAccIterator:: + RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent, + nsIAtom* aRelAttr) : + mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), + mBindingParent(nullptr), mIndex(0) +{ + mBindingParent = aDependentContent->GetBindingParent(); + nsIAtom* IDAttr = mBindingParent ? + nsGkAtoms::anonid : nsGkAtoms::id; + + nsAutoString id; + if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id)) + mProviders = mDocument->mDependentIDsHash.Get(id); +} + +Accessible* +RelatedAccIterator::Next() +{ + if (!mProviders) + return nullptr; + + while (mIndex < mProviders->Length()) { + DocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++]; + + // Return related accessible for the given attribute and if the provider + // content is in the same binding in the case of XBL usage. + if (provider->mRelAttr == mRelAttr) { + nsIContent* bindingParent = provider->mContent->GetBindingParent(); + bool inScope = mBindingParent == bindingParent || + mBindingParent == provider->mContent; + + if (inScope) { + Accessible* related = mDocument->GetAccessible(provider->mContent); + if (related) + return related; + + // If the document content is pointed by relation then return the document + // itself. + if (provider->mContent == mDocument->GetContent()) + return mDocument; + } + } + } + + return nullptr; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLabelIterator +//////////////////////////////////////////////////////////////////////////////// + +HTMLLabelIterator:: + HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible, + LabelFilter aFilter) : + mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for), + mAcc(aAccessible), mLabelFilter(aFilter) +{ +} + +bool +HTMLLabelIterator::IsLabel(Accessible* aLabel) +{ + dom::HTMLLabelElement* labelEl = + dom::HTMLLabelElement::FromContent(aLabel->GetContent()); + return labelEl && labelEl->GetControl() == mAcc->GetContent(); +} + +Accessible* +HTMLLabelIterator::Next() +{ + // Get either