From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- accessible/base/ARIAMap.cpp | 1687 +++++++++++++++++++++++ accessible/base/ARIAMap.h | 335 +++++ accessible/base/ARIAStateMap.cpp | 334 +++++ accessible/base/ARIAStateMap.h | 66 + accessible/base/AccAttributes.cpp | 270 ++++ accessible/base/AccAttributes.h | 293 ++++ accessible/base/AccEvent.cpp | 256 ++++ accessible/base/AccEvent.h | 562 ++++++++ accessible/base/AccGroupInfo.cpp | 397 ++++++ accessible/base/AccGroupInfo.h | 101 ++ accessible/base/AccIterator.cpp | 360 +++++ accessible/base/AccIterator.h | 328 +++++ accessible/base/AccTypes.h | 98 ++ accessible/base/Asserts.cpp | 29 + accessible/base/CacheConstants.h | 255 ++++ accessible/base/CachedTableAccessible.cpp | 429 ++++++ accessible/base/CachedTableAccessible.h | 294 ++++ accessible/base/DocManager.cpp | 562 ++++++++ accessible/base/DocManager.h | 193 +++ accessible/base/EmbeddedObjCollector.cpp | 62 + accessible/base/EmbeddedObjCollector.h | 68 + accessible/base/EventQueue.cpp | 436 ++++++ accessible/base/EventQueue.h | 83 ++ accessible/base/EventTree.cpp | 99 ++ accessible/base/EventTree.h | 61 + accessible/base/Filters.cpp | 25 + accessible/base/Filters.h | 36 + accessible/base/FocusManager.cpp | 462 +++++++ accessible/base/FocusManager.h | 169 +++ accessible/base/HTMLMarkupMap.h | 444 +++++++ accessible/base/IDSet.h | 129 ++ accessible/base/Logging.cpp | 992 ++++++++++++++ accessible/base/Logging.h | 236 ++++ accessible/base/MathMLMarkupMap.h | 113 ++ accessible/base/NotificationController.cpp | 1107 ++++++++++++++++ accessible/base/NotificationController.h | 396 ++++++ accessible/base/Pivot.cpp | 331 +++++ accessible/base/Pivot.h | 141 ++ accessible/base/Platform.h | 136 ++ accessible/base/Relation.h | 105 ++ accessible/base/RelationTypeGen.py | 41 + accessible/base/RelationTypeMap.h | 90 ++ accessible/base/RoleHGen.py | 42 + accessible/base/RoleMap.h | 1546 +++++++++++++++++++++ accessible/base/SelectionManager.cpp | 246 ++++ accessible/base/SelectionManager.h | 141 ++ accessible/base/States.h | 305 +++++ accessible/base/Statistics.h | 42 + accessible/base/StyleInfo.cpp | 50 + accessible/base/StyleInfo.h | 36 + accessible/base/TextAttrs.cpp | 816 ++++++++++++ accessible/base/TextAttrs.h | 440 ++++++ accessible/base/TextLeafRange.cpp | 1990 ++++++++++++++++++++++++++++ accessible/base/TextLeafRange.h | 360 +++++ accessible/base/TextRange-inl.h | 25 + accessible/base/TextRange.cpp | 376 ++++++ accessible/base/TextRange.h | 164 +++ accessible/base/TextUpdater.cpp | 215 +++ accessible/base/TextUpdater.h | 95 ++ accessible/base/TreeWalker.cpp | 348 +++++ accessible/base/TreeWalker.h | 142 ++ accessible/base/XULMap.h | 115 ++ accessible/base/moz.build | 122 ++ accessible/base/nsAccCache.h | 24 + accessible/base/nsAccUtils.cpp | 626 +++++++++ accessible/base/nsAccUtils.h | 299 +++++ accessible/base/nsAccessibilityService.cpp | 1933 +++++++++++++++++++++++++++ accessible/base/nsAccessibilityService.h | 492 +++++++ accessible/base/nsCoreUtils.cpp | 622 +++++++++ accessible/base/nsCoreUtils.h | 329 +++++ accessible/base/nsEventShell.cpp | 81 ++ accessible/base/nsEventShell.h | 66 + accessible/base/nsTextEquivUtils.cpp | 360 +++++ accessible/base/nsTextEquivUtils.h | 182 +++ 74 files changed, 25241 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/AccAttributes.cpp create mode 100644 accessible/base/AccAttributes.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/Asserts.cpp create mode 100644 accessible/base/CacheConstants.h create mode 100644 accessible/base/CachedTableAccessible.cpp create mode 100644 accessible/base/CachedTableAccessible.h 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/HTMLMarkupMap.h create mode 100644 accessible/base/IDSet.h create mode 100644 accessible/base/Logging.cpp create mode 100644 accessible/base/Logging.h create mode 100644 accessible/base/MathMLMarkupMap.h create mode 100644 accessible/base/NotificationController.cpp create mode 100644 accessible/base/NotificationController.h create mode 100644 accessible/base/Pivot.cpp create mode 100644 accessible/base/Pivot.h create mode 100644 accessible/base/Platform.h create mode 100644 accessible/base/Relation.h create mode 100644 accessible/base/RelationTypeGen.py create mode 100644 accessible/base/RelationTypeMap.h create mode 100644 accessible/base/RoleHGen.py 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/TextLeafRange.cpp create mode 100644 accessible/base/TextLeafRange.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/XULMap.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/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 0000000000..01cc5d0417 --- /dev/null +++ b/accessible/base/ARIAMap.cpp @@ -0,0 +1,1687 @@ +/* -*- 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 "AccAttributes.h" +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "mozilla/a11y/Role.h" +#include "States.h" + +#include "nsAttrName.h" +#include "nsWhitespaceTokenizer.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/dom/Element.h" + +#include "nsUnicharUtils.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". + * + * Note: the list must remain alphabetically ordered to support binary search. + */ + +static const nsRoleMapEntry sWAIRoleMaps[] = { + // clang-format off + { // alert + nsGkAtoms::alert, + roles::ALERT, + kUseMapRole, + eNoValue, + eNoAction, +#if defined(XP_MACOSX) + eAssertiveLiveAttr, +#else + eNoLiveAttr, +#endif + 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::ARTICLE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eReadonlyUntilEditable + }, + { // banner + nsGkAtoms::banner, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // blockquote + nsGkAtoms::blockquote, + roles::BLOCKQUOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // button + nsGkAtoms::button, + roles::PUSHBUTTON, + kUseMapRole, + eNoValue, + ePressAction, + eNoLiveAttr, + eButton, + kNoReqStates + // eARIAPressed is auto applied on any button + }, + { // caption + nsGkAtoms::caption, + roles::CAPTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // cell + nsGkAtoms::cell, + roles::CELL, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTableCell, + kNoReqStates + }, + { // checkbox + nsGkAtoms::checkbox, + roles::CHECKBUTTON, + kUseMapRole, + eNoValue, + eCheckUncheckAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableMixed, + eARIAReadonly + }, + { // code + nsGkAtoms::code, + roles::CODE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // columnheader + nsGkAtoms::columnheader, + roles::COLUMNHEADER, + kUseMapRole, + eNoValue, + eSortAction, + eNoLiveAttr, + eTableCell, + kNoReqStates, + eARIASelectableIfDefined, + eARIAReadonly + }, + { // combobox, which consists of text input and popup + nsGkAtoms::combobox, + roles::EDITCOMBOBOX, + kUseMapRole, + eNoValue, + eOpenCloseAction, + eNoLiveAttr, + eCombobox, + states::COLLAPSED | states::HASPOPUP, + eARIAAutoComplete, + eARIAReadonly, + eARIAOrientation + }, + { // comment + nsGkAtoms::comment, + roles::COMMENT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // complementary + nsGkAtoms::complementary, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // contentinfo + nsGkAtoms::contentinfo, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // definition + nsGkAtoms::definition, + roles::DEFINITION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // deletion + nsGkAtoms::deletion, + roles::CONTENT_DELETION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // dialog + nsGkAtoms::dialog, + roles::DIALOG, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // directory + nsGkAtoms::directory, + roles::LIST, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eList, + states::READONLY + }, + { // doc-abstract + nsGkAtoms::docAbstract, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-acknowledgments + nsGkAtoms::docAcknowledgments, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-afterword + nsGkAtoms::docAfterword, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-appendix + nsGkAtoms::docAppendix, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-backlink + nsGkAtoms::docBacklink, + roles::LINK, + kUseMapRole, + eNoValue, + eJumpAction, + eNoLiveAttr, + kGenericAccType, + states::LINKED + }, + { // doc-biblioentry + nsGkAtoms::docBiblioentry, + roles::LISTITEM, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::READONLY + }, + { // doc-bibliography + nsGkAtoms::docBibliography, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-biblioref + nsGkAtoms::docBiblioref, + roles::LINK, + kUseMapRole, + eNoValue, + eJumpAction, + eNoLiveAttr, + kGenericAccType, + states::LINKED + }, + { // doc-chapter + nsGkAtoms::docChapter, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-colophon + nsGkAtoms::docColophon, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-conclusion + nsGkAtoms::docConclusion, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-cover + nsGkAtoms::docCover, + roles::GRAPHIC, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-credit + nsGkAtoms::docCredit, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-credits + nsGkAtoms::docCredits, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-dedication + nsGkAtoms::docDedication, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-endnote + nsGkAtoms::docEndnote, + roles::LISTITEM, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::READONLY + }, + { // doc-endnotes + nsGkAtoms::docEndnotes, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-epigraph + nsGkAtoms::docEpigraph, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-epilogue + nsGkAtoms::docEpilogue, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-errata + nsGkAtoms::docErrata, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-example + nsGkAtoms::docExample, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-footnote + nsGkAtoms::docFootnote, + roles::FOOTNOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-foreword + nsGkAtoms::docForeword, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-glossary + nsGkAtoms::docGlossary, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-glossref + nsGkAtoms::docGlossref, + roles::LINK, + kUseMapRole, + eNoValue, + eJumpAction, + eNoLiveAttr, + kGenericAccType, + states::LINKED + }, + { // doc-index + nsGkAtoms::docIndex, + roles::NAVIGATION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-introduction + nsGkAtoms::docIntroduction, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-noteref + nsGkAtoms::docNoteref, + roles::LINK, + kUseMapRole, + eNoValue, + eJumpAction, + eNoLiveAttr, + kGenericAccType, + states::LINKED + }, + { // doc-notice + nsGkAtoms::docNotice, + roles::NOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-pagebreak + nsGkAtoms::docPagebreak, + roles::SEPARATOR, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-pagelist + nsGkAtoms::docPagelist, + roles::NAVIGATION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-part + nsGkAtoms::docPart, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-preface + nsGkAtoms::docPreface, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-prologue + nsGkAtoms::docPrologue, + roles::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // doc-pullquote + nsGkAtoms::docPullquote, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-qna + nsGkAtoms::docQna, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-subtitle + nsGkAtoms::docSubtitle, + roles::HEADING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-tip + nsGkAtoms::docTip, + roles::NOTE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // doc-toc + nsGkAtoms::docToc, + roles::NAVIGATION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // document + nsGkAtoms::document, + roles::NON_NATIVE_DOCUMENT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eReadonlyUntilEditable + }, + { // emphasis + nsGkAtoms::emphasis, + roles::EMPHASIS, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // feed + nsGkAtoms::feed, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // figure + nsGkAtoms::figure, + roles::FIGURE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // form + nsGkAtoms::form, + roles::FORM, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // generic + nsGkAtoms::generic, + roles::SECTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // graphics-document + nsGkAtoms::graphicsDocument, + roles::NON_NATIVE_DOCUMENT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eReadonlyUntilEditable + }, + { // graphics-object + nsGkAtoms::graphicsObject, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // graphics-symbol + nsGkAtoms::graphicsSymbol, + roles::GRAPHIC, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // grid + nsGkAtoms::grid, + roles::TABLE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eSelect | eTable, + kNoReqStates, + eARIAMultiSelectable, + eARIAReadonly, + eFocusableUntilDisabled + }, + { // gridcell + nsGkAtoms::gridcell, + roles::GRID_CELL, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eTableCell, + kNoReqStates, + eARIASelectable, + eARIAReadonly + }, + { // group + nsGkAtoms::group, + roles::GROUPING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // heading + nsGkAtoms::heading, + roles::HEADING, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // image + nsGkAtoms::image, + roles::GRAPHIC, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // img + nsGkAtoms::img, + roles::GRAPHIC, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // insertion + nsGkAtoms::insertion, + roles::CONTENT_INSERTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // 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::LANDMARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + kNoReqStates + }, + { // mark + nsGkAtoms::mark, + roles::MARK, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // 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 + }, + { // menuitemcheckbox + nsGkAtoms::menuitemcheckbox, + roles::CHECK_MENU_ITEM, + kUseMapRole, + eNoValue, + eClickAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableMixed, + eARIAReadonly + }, + { // menuitemradio + nsGkAtoms::menuitemradio, + roles::RADIO_MENU_ITEM, + kUseMapRole, + eNoValue, + eClickAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableBool, + eARIAReadonly + }, + { // meter + nsGkAtoms::meter, + roles::METER, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::READONLY + }, + { // navigation + nsGkAtoms::navigation, + roles::LANDMARK, + kUseMapRole, + 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 + }, + { // paragraph + nsGkAtoms::paragraph, + roles::PARAGRAPH, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // 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, + eARIAReadonly + }, + { // region + nsGkAtoms::region, + roles::REGION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + eLandmark, + 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, + eARIAReadonly + }, + { // scrollbar + nsGkAtoms::scrollbar, + roles::SCROLLBAR, + kUseMapRole, + eHasValueMinMax, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::VERTICAL, + eARIAOrientation, + eARIAReadonly + }, + { // search + nsGkAtoms::search, + roles::LANDMARK, + kUseMapRole, + 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, + eHasValueMinMaxIfFocusable, + 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 + }, + { // strong + nsGkAtoms::strong, + roles::STRONG, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // subscript + nsGkAtoms::subscript, + roles::SUBSCRIPT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType + }, + { // suggestion + nsGkAtoms::suggestion, + roles::SUGGESTION, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + }, + { // superscript + nsGkAtoms::superscript, + roles::SUPERSCRIPT, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType + }, + { // switch + nsGkAtoms::svgSwitch, + roles::SWITCH, + kUseMapRole, + eNoValue, + eCheckUncheckAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIACheckableBool, + eARIAReadonly + }, + { // 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, + eARIAMultiSelectable + }, + { // tabpanel + nsGkAtoms::tabpanel, + roles::PROPERTYPAGE, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates + }, + { // term + nsGkAtoms::term, + roles::TERM, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kGenericAccType, + states::READONLY + }, + { // textbox + nsGkAtoms::textbox, + roles::ENTRY, + kUseMapRole, + eNoValue, + eActivateAction, + eNoLiveAttr, + kGenericAccType, + kNoReqStates, + eARIAAutoComplete, + eARIAMultiline, + eARIAReadonlyOrEditable + }, + { // time + nsGkAtoms::time, + roles::TIME, + kUseMapRole, + eNoValue, + eNoAction, + eNoLiveAttr, + kNoReqStates + }, + { // 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, + kNoReqStates, + eARIAReadonly, + 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 + } + // clang-format on +}; + +static const nsRoleMapEntry sLandmarkRoleMap = { + nsGkAtoms::_empty, roles::NOTHING, kUseNativeRole, eNoValue, + eNoAction, eNoLiveAttr, kGenericAccType, kNoReqStates}; + +nsRoleMapEntry aria::gEmptyRoleMap = { + nsGkAtoms::_empty, roles::TEXT_CONTAINER, 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, eARIACurrent, eARIADisabled, + eARIAExpanded, // Currently under spec review but precedent exists + eARIAHasPopup, // Note this is a tokenised attribute starting in ARIA 1.1 + 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 { + const nsStaticAtom* const attributeName; + const uint8_t characteristics; +}; + +static const AttrCharacteristics gWAIUnivAttrMap[] = { + // clang-format off + {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_colcount, ATTR_VALINT }, + {nsGkAtoms::aria_colindex, ATTR_VALINT }, + {nsGkAtoms::aria_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_current, ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL }, + {nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + // XXX Ideally, aria-description shouldn't expose a description object + // attribute (i.e. it should have ATTR_BYPASSOBJ). However, until the + // description-from attribute is implemented (bug 1726087), clients such as + // NVDA depend on the description object attribute to work out whether the + // accDescription originated from aria-description. + {nsGkAtoms::aria_description, 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_IF_FALSE | 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_GLOBAL }, + {nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {nsGkAtoms::aria_rowcount, ATTR_VALINT }, + {nsGkAtoms::aria_rowindex, ATTR_VALINT }, + {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 } + // clang-format on +}; + +const nsRoleMapEntry* aria::GetRoleMap(dom::Element* aEl) { + return GetRoleMapFromIndex(GetRoleMapIndex(aEl)); +} + +uint8_t aria::GetRoleMapIndex(dom::Element* aEl) { + nsAutoString roles; + if (!aEl || !nsAccUtils::GetARIAAttr(aEl, 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; + auto comparator = [&role](const nsRoleMapEntry& aEntry) { + return Compare(role, aEntry.ARIARoleString(), + nsCaseInsensitiveStringComparator); + }; + if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps), comparator, + &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 { + uint8_t index = aRoleMapEntry - sWAIRoleMaps; + MOZ_ASSERT(aria::IsRoleMapIndexValid(index)); + return index; + } +} + +bool aria::IsRoleMapIndexValid(uint8_t aRoleMapIndex) { + switch (aRoleMapIndex) { + case NO_ROLE_MAP_ENTRY_INDEX: + case EMPTY_ROLE_MAP_ENTRY_INDEX: + case LANDMARK_ROLE_MAP_ENTRY_INDEX: + return true; + } + return aRoleMapIndex < ArrayLength(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(nsAtom* 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 && aContent->IsElement() && + nsAccUtils::ARIAAttrValueIs(aContent->AsElement(), + nsGkAtoms::aria_hidden, nsGkAtoms::_true, + eCaseMatters); +} + +//////////////////////////////////////////////////////////////////////////////// +// AttrIterator class + +AttrIterator::AttrIterator(nsIContent* aContent) + : mElement(dom::Element::FromNode(aContent)), + mIteratingDefaults(false), + mAttrIdx(0), + mAttrCharacteristics(0) { + mAttrs = mElement ? &mElement->GetAttrs() : nullptr; + mAttrCount = mAttrs ? mAttrs->AttrCount() : 0; +} + +bool AttrIterator::Next() { + while (mAttrIdx < mAttrCount) { + const nsAttrName* attr = mAttrs->GetSafeAttrNameAt(mAttrIdx); + mAttrIdx++; + if (attr->NamespaceEquals(kNameSpaceID_None)) { + mAttrAtom = attr->Atom(); + nsDependentAtomString attrStr(mAttrAtom); + if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // Not ARIA + + if (mIteratingDefaults) { + if (mOverriddenAttrs.Contains(mAttrAtom)) { + continue; + } + } else { + mOverriddenAttrs.Insert(mAttrAtom); + } + + // AttrCharacteristicsFor has to search for the entry, so cache it here + // rather than having to search again later. + mAttrCharacteristics = aria::AttrCharacteristicsFor(mAttrAtom); + if (mAttrCharacteristics & ATTR_BYPASSOBJ) { + continue; // No need to handle exposing as obj attribute here + } + + if ((mAttrCharacteristics & ATTR_VALTOKEN) && + !nsAccUtils::HasDefinedARIAToken(mAttrs, mAttrAtom)) { + continue; // only expose token based attributes if they are defined + } + + if ((mAttrCharacteristics & ATTR_BYPASSOBJ_IF_FALSE) && + mAttrs->AttrValueIs(kNameSpaceID_None, mAttrAtom, nsGkAtoms::_false, + eCaseMatters)) { + continue; // only expose token based attribute if value is not 'false'. + } + + return true; + } + } + + mAttrCharacteristics = 0; + mAttrAtom = nullptr; + + if (const auto* defaults = nsAccUtils::GetARIADefaults(mElement); + !mIteratingDefaults && defaults) { + mIteratingDefaults = true; + mAttrs = defaults; + mAttrCount = mAttrs->AttrCount(); + mAttrIdx = 0; + return Next(); + } + + return false; +} + +nsAtom* AttrIterator::AttrName() const { return mAttrAtom; } + +void AttrIterator::AttrValue(nsAString& aAttrValue) const { + nsAutoString value; + if (mAttrs->GetAttr(mAttrAtom, value)) { + if (mAttrCharacteristics & ATTR_VALTOKEN) { + nsAtom* normalizedValue = + nsAccUtils::NormalizeARIAToken(mAttrs, mAttrAtom); + if (normalizedValue) { + nsDependentAtomString normalizedValueStr(normalizedValue); + aAttrValue.Assign(normalizedValueStr); + return; + } + } + aAttrValue.Assign(value); + } +} + +bool AttrIterator::ExposeAttr(AccAttributes* aTargetAttrs) const { + if (mAttrCharacteristics & ATTR_VALTOKEN) { + nsAtom* normalizedValue = nsAccUtils::NormalizeARIAToken(mAttrs, mAttrAtom); + if (normalizedValue) { + aTargetAttrs->SetAttribute(mAttrAtom, normalizedValue); + return true; + } + } else if (mAttrCharacteristics & ATTR_VALINT) { + int32_t intVal; + if (nsCoreUtils::GetUIntAttrValue(mAttrs->GetAttr(mAttrAtom), &intVal)) { + aTargetAttrs->SetAttribute(mAttrAtom, intVal); + return true; + } + if (mAttrAtom == nsGkAtoms::aria_colcount || + mAttrAtom == nsGkAtoms::aria_rowcount) { + // These attributes allow a value of -1. + if (mAttrs->AttrValueIs(kNameSpaceID_None, mAttrAtom, u"-1"_ns, + eCaseMatters)) { + aTargetAttrs->SetAttribute(mAttrAtom, -1); + return true; + } + } + return false; // Invalid value. + } + nsAutoString value; + if (mAttrs->GetAttr(mAttrAtom, value)) { + aTargetAttrs->SetAttribute(mAttrAtom, std::move(value)); + return true; + } + return false; +} diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h new file mode 100644 index 0000000000..30cc1f0814 --- /dev/null +++ b/accessible/base/ARIAMap.h @@ -0,0 +1,335 @@ +/* -*- 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 "nsAtom.h" +#include "nsIContent.h" +#include "nsTHashSet.h" + +class nsINode; + +namespace mozilla::dom { +class Element; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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, + + /** + * Value interface is implemented, but only if the element is focusable. + * For instance, in ARIA 1.1 the ability for authors to create adjustable + * splitters was provided by supporting the value interface on separators + * that are focusable. Non-focusable separators expose no value information. + */ + eHasValueMinMaxIfFocusable +}; + +//////////////////////////////////////////////////////////////////////////////// +// 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, + eAssertiveLiveAttr +}; + +//////////////////////////////////////////////////////////////////////////////// +// 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; + +/** + * Indicates that the attribute should have an integer value. + */ +const uint8_t ATTR_VALINT = 0x1 << 4; + +//////////////////////////////////////////////////////////////////////////////// +// 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(nsAtom* 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" + nsStaticAtom* const 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; + + // LocalAccessible 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 { +class AccAttributes; + +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); + +/** + * Determine whether a role map entry index is valid. + */ +bool IsRoleMapIndexValid(uint8_t aRoleMapIndex); + +/** + * Return accessible state from ARIA universal states applied to the given + * element. + */ +uint64_t UniversalStatesFor(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(nsAtom* 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); + + bool Next(); + + nsAtom* AttrName() const; + + void AttrValue(nsAString& aAttrValue) const; + + /** + * Expose this ARIA attribute in a specified AccAttributes. The appropriate + * type will be used for the attribute; e.g. an atom for a token value. + */ + bool ExposeAttr(AccAttributes* aTargetAttrs) const; + + private: + AttrIterator() = delete; + AttrIterator(const AttrIterator&) = delete; + AttrIterator& operator=(const AttrIterator&) = delete; + + dom::Element* mElement; + + bool mIteratingDefaults; + nsTHashSet> mOverriddenAttrs; + + const AttrArray* mAttrs; + uint32_t mAttrIdx; + uint32_t mAttrCount; + RefPtr mAttrAtom; + uint8_t mAttrCharacteristics; +}; + +} // namespace aria +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp new file mode 100644 index 0000000000..6bf20cf1cc --- /dev/null +++ b/accessible/base/ARIAStateMap.cpp @@ -0,0 +1,334 @@ +/* -*- 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. + nsStaticAtom* const mAttrName; + + // States if the attribute value is matched to the enum value. Used as + // Element::AttrValuesArray, last item must be nullptr. + nsStaticAtom* 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(nsAtom* 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. + nsAtom* 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 eARIACurrent: { + static const TokenTypeData data(nsGkAtoms::aria_current, eBoolType, 0, + states::CURRENT); + + 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 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 (!nsAccUtils::HasARIAAttr(aElement, nsGkAtoms::aria_valuenow) && + !nsAccUtils::HasARIAAttr(aElement, nsGkAtoms::aria_valuetext)) { + *aState |= states::MIXED; + } + + return true; + } + + case eFocusableUntilDisabled: { + if (!nsAccUtils::HasDefinedARIAToken(aElement, + nsGkAtoms::aria_disabled) || + nsAccUtils::ARIAAttrValueIs(aElement, 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 (nsAccUtils::FindARIAAttrValueIn(aElement, 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 (nsAccUtils::ARIAAttrValueIs(aElement, aData.mAttrName, nsGkAtoms::mixed, + eCaseMatters)) { + if (aData.mType & eMixedType) { + *aState |= aData.mPermanentState | states::MIXED; + } else { // unsupported use of 'mixed' is an authoring error + *aState |= aData.mPermanentState | aData.mFalseState; + } + return; + } + + if (nsAccUtils::ARIAAttrValueIs(aElement, 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 0000000000..20490aa901 --- /dev/null +++ b/accessible/base/ARIAStateMap.h @@ -0,0 +1,66 @@ +/* -*- 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, + eARIACurrent, + eARIADisabled, + eARIAExpanded, + eARIAHasPopup, + eARIAInvalid, + eARIAModal, + eARIAMultiline, + eARIAMultiSelectable, + eARIAOrientation, + eARIAPressed, + eARIAReadonly, + eARIAReadonlyOrEditable, + 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/AccAttributes.cpp b/accessible/base/AccAttributes.cpp new file mode 100644 index 0000000000..4018f09074 --- /dev/null +++ b/accessible/base/AccAttributes.cpp @@ -0,0 +1,270 @@ +/* -*- 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/. */ + +#include "AccAttributes.h" +#include "StyleInfo.h" +#include "mozilla/ToString.h" +#include "nsAtom.h" + +using namespace mozilla::a11y; + +bool AccAttributes::GetAttribute(nsAtom* aAttrName, + nsAString& aAttrValue) const { + if (auto value = mData.Lookup(aAttrName)) { + StringFromValueAndName(aAttrName, *value, aAttrValue); + return true; + } + + return false; +} + +void AccAttributes::StringFromValueAndName(nsAtom* aAttrName, + const AttrValueType& aValue, + nsAString& aValueString) { + aValueString.Truncate(); + + aValue.match( + [&aValueString](const bool& val) { + aValueString.Assign(val ? u"true" : u"false"); + }, + [&aValueString](const float& val) { + aValueString.AppendFloat(val * 100); + aValueString.Append(u"%"); + }, + [&aValueString](const double& val) { aValueString.AppendFloat(val); }, + [&aValueString](const int32_t& val) { aValueString.AppendInt(val); }, + [&aValueString](const RefPtr& val) { + val->ToString(aValueString); + }, + [&aValueString](const nsTArray& val) { + if (const size_t len = val.Length()) { + for (size_t i = 0; i < len - 1; i++) { + aValueString.AppendInt(val[i]); + aValueString.Append(u", "); + } + aValueString.AppendInt(val[len - 1]); + } else { + // The array is empty + NS_WARNING( + "Hmm, should we have used a DeleteEntry() for this instead?"); + aValueString.Append(u"[ ]"); + } + }, + [&aValueString](const CSSCoord& val) { + aValueString.AppendFloat(val); + aValueString.Append(u"px"); + }, + [&aValueString](const FontSize& val) { + aValueString.AppendInt(val.mValue); + aValueString.Append(u"pt"); + }, + [&aValueString](const Color& val) { + StyleInfo::FormatColor(val.mValue, aValueString); + }, + [&aValueString](const DeleteEntry& val) { + aValueString.Append(u"-delete-entry-"); + }, + [&aValueString](const UniquePtr& val) { + aValueString.Assign(*val); + }, + [&aValueString](const RefPtr& val) { + aValueString.Assign(u"AccAttributes{...}"); + }, + [&aValueString](const uint64_t& val) { aValueString.AppendInt(val); }, + [&aValueString](const UniquePtr& val) { + aValueString.Assign(u"AccGroupInfo{...}"); + }, + [&aValueString](const UniquePtr& val) { + aValueString.AppendPrintf("Matrix4x4=%s", ToString(*val).c_str()); + }, + [&aValueString](const nsTArray& val) { + if (const size_t len = val.Length()) { + for (size_t i = 0; i < len - 1; i++) { + aValueString.AppendInt(val[i]); + aValueString.Append(u", "); + } + aValueString.AppendInt(val[len - 1]); + } else { + // The array is empty + NS_WARNING( + "Hmm, should we have used a DeleteEntry() for this instead?"); + aValueString.Append(u"[ ]"); + } + }); +} + +void AccAttributes::Update(AccAttributes* aOther) { + for (auto iter = aOther->mData.Iter(); !iter.Done(); iter.Next()) { + if (iter.Data().is()) { + mData.Remove(iter.Key()); + } else { + mData.InsertOrUpdate(iter.Key(), std::move(iter.Data())); + } + iter.Remove(); + } +} + +bool AccAttributes::Equal(const AccAttributes* aOther) const { + if (Count() != aOther->Count()) { + return false; + } + for (auto iter = mData.ConstIter(); !iter.Done(); iter.Next()) { + const auto otherEntry = aOther->mData.Lookup(iter.Key()); + if (!otherEntry) { + return false; + } + if (iter.Data().is>()) { + // Because we store nsString in a UniquePtr, we must handle it specially + // so we compare the string and not the pointer. + if (!otherEntry->is>()) { + return false; + } + const auto& thisStr = iter.Data().as>(); + const auto& otherStr = otherEntry->as>(); + if (*thisStr != *otherStr) { + return false; + } + } else if (iter.Data() != otherEntry.Data()) { + return false; + } + } + return true; +} + +void AccAttributes::CopyTo(AccAttributes* aDest) const { + for (auto iter = mData.ConstIter(); !iter.Done(); iter.Next()) { + iter.Data().match( + [&iter, &aDest](const bool& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const float& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const double& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const int32_t& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const RefPtr& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [](const nsTArray& val) { + // We don't copy arrays. + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing an array"); + }, + [&iter, &aDest](const CSSCoord& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const FontSize& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [&iter, &aDest](const Color& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [](const DeleteEntry& val) { + // We don't copy DeleteEntry. + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing a DeleteEntry"); + }, + [&iter, &aDest](const UniquePtr& val) { + aDest->SetAttributeStringCopy(iter.Key(), *val); + }, + [](const RefPtr& val) { + // We don't copy nested AccAttributes. + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing an AccAttributes"); + }, + [&iter, &aDest](const uint64_t& val) { + aDest->mData.InsertOrUpdate(iter.Key(), AsVariant(val)); + }, + [](const UniquePtr& val) { + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing an AccGroupInfo"); + }, + [](const UniquePtr& val) { + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing a matrix"); + }, + [](const nsTArray& val) { + // We don't copy arrays. + MOZ_ASSERT_UNREACHABLE( + "Trying to copy an AccAttributes containing an array"); + }); + } +} + +#ifdef A11Y_LOG +void AccAttributes::DebugPrint(const char* aPrefix, + const AccAttributes& aAttributes) { + nsAutoString prettyString; + prettyString.AssignLiteral("{\n"); + for (const auto& iter : aAttributes) { + nsAutoString name; + iter.NameAsString(name); + + nsAutoString value; + iter.ValueAsString(value); + prettyString.AppendLiteral(" "); + prettyString.Append(name); + prettyString.AppendLiteral(": "); + prettyString.Append(value); + prettyString.AppendLiteral("\n"); + } + + prettyString.AppendLiteral("}"); + printf("%s %s\n", aPrefix, NS_ConvertUTF16toUTF8(prettyString).get()); +} +#endif + +size_t AccAttributes::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + size_t size = + aMallocSizeOf(this) + mData.ShallowSizeOfExcludingThis(aMallocSizeOf); + + for (auto iter : *this) { + size += iter.SizeOfExcludingThis(aMallocSizeOf); + } + + return size; +} + +size_t AccAttributes::Entry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + size_t size = 0; + + // We don't count the size of Name() since it's counted by the atoms table + // memory reporter. + + if (mValue->is>()) { + size += mValue->as>().ShallowSizeOfExcludingThis( + aMallocSizeOf); + } else if (mValue->is>()) { + // String data will never be shared. + size += mValue->as>()->SizeOfIncludingThisIfUnshared( + aMallocSizeOf); + } else if (mValue->is>()) { + size += + mValue->as>()->SizeOfIncludingThis(aMallocSizeOf); + } else if (mValue->is>()) { + size += mValue->as>()->SizeOfIncludingThis( + aMallocSizeOf); + } else if (mValue->is>()) { + size += aMallocSizeOf(mValue->as>().get()); + } else if (mValue->is>()) { + size += mValue->as>().ShallowSizeOfExcludingThis( + aMallocSizeOf); + } else { + // This type is stored directly and already counted or is an atom and + // stored and counted in the atoms table. + // Assert that we have exhausted all the remaining variant types. + MOZ_ASSERT(mValue->is>() || mValue->is() || + mValue->is() || mValue->is() || + mValue->is() || mValue->is() || + mValue->is() || mValue->is() || + mValue->is() || mValue->is()); + } + + return size; +} diff --git a/accessible/base/AccAttributes.h b/accessible/base/AccAttributes.h new file mode 100644 index 0000000000..0d7610b358 --- /dev/null +++ b/accessible/base/AccAttributes.h @@ -0,0 +1,293 @@ +/* -*- 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 AccAttributes_h_ +#define AccAttributes_h_ + +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/a11y/AccGroupInfo.h" +#include "mozilla/Variant.h" +#include "nsTHashMap.h" +#include "nsStringFwd.h" +#include "mozilla/gfx/Matrix.h" + +class nsVariant; + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { + +namespace dom { +class Element; +} + +namespace a11y { + +struct FontSize { + int32_t mValue; + + bool operator==(const FontSize& aOther) const { + return mValue == aOther.mValue; + } + + bool operator!=(const FontSize& aOther) const { + return mValue != aOther.mValue; + } +}; + +struct Color { + nscolor mValue; + + bool operator==(const Color& aOther) const { return mValue == aOther.mValue; } + + bool operator!=(const Color& aOther) const { return mValue != aOther.mValue; } +}; + +// A special type. If an entry has a value of this type, it instructs the +// target instance of an Update to remove the entry with the same key value. +struct DeleteEntry { + DeleteEntry() : mValue(true) {} + bool mValue; + + bool operator==(const DeleteEntry& aOther) const { return true; } + + bool operator!=(const DeleteEntry& aOther) const { return false; } +}; + +class AccAttributes { + // Warning! An AccAttributes can contain another AccAttributes. This is + // intended for object and text attributes. However, the nested + // AccAttributes should never itself contain another AccAttributes, nor + // should it create a cycle. We don't do cycle collection here for + // performance reasons, so violating this rule will cause leaks! + using AttrValueType = + Variant, nsTArray, + CSSCoord, FontSize, Color, DeleteEntry, UniquePtr, + RefPtr, uint64_t, UniquePtr, + UniquePtr, nsTArray>; + static_assert(sizeof(AttrValueType) <= 16); + using AtomVariantMap = nsTHashMap, AttrValueType>; + + protected: + ~AccAttributes() = default; + + public: + AccAttributes() = default; + AccAttributes(const AccAttributes&) = delete; + AccAttributes& operator=(const AccAttributes&) = delete; + + NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::AccAttributes) + + template + void SetAttribute(nsAtom* aAttrName, T&& aAttrValue) { + using ValType = + std::remove_const_t>; + if constexpr (std::is_convertible_v) { + static_assert(std::is_rvalue_reference_v, + "Please only move strings into this function. To make a " + "copy, use SetAttributeStringCopy."); + UniquePtr value = MakeUnique(std::move(aAttrValue)); + mData.InsertOrUpdate(aAttrName, AsVariant(std::move(value))); + } else if constexpr (std::is_same_v) { + UniquePtr value = MakeUnique(aAttrValue); + mData.InsertOrUpdate(aAttrName, AsVariant(std::move(value))); + } else if constexpr (std::is_same_v) { + UniquePtr value(aAttrValue); + mData.InsertOrUpdate(aAttrName, AsVariant(std::move(value))); + } else if constexpr (std::is_convertible_v) { + mData.InsertOrUpdate(aAttrName, AsVariant(RefPtr(aAttrValue))); + } else { + mData.InsertOrUpdate(aAttrName, AsVariant(std::forward(aAttrValue))); + } + } + + void SetAttributeStringCopy(nsAtom* aAttrName, nsString aAttrValue) { + SetAttribute(aAttrName, std::move(aAttrValue)); + } + + template + Maybe GetAttribute(nsAtom* aAttrName) const { + if (auto value = mData.Lookup(aAttrName)) { + if constexpr (std::is_same_v) { + if (value->is>()) { + const T& val = *(value->as>().get()); + return SomeRef(val); + } + } else if constexpr (std::is_same_v) { + if (value->is>()) { + const T& val = *(value->as>()); + return SomeRef(val); + } + } else { + if (value->is()) { + const T& val = value->as(); + return SomeRef(val); + } + } + } + return Nothing(); + } + + template + RefPtr GetAttributeRefPtr(nsAtom* aAttrName) const { + if (auto value = mData.Lookup(aAttrName)) { + if (value->is>()) { + RefPtr ref = value->as>(); + return ref; + } + } + return nullptr; + } + + template + Maybe GetMutableAttribute(nsAtom* aAttrName) const { + static_assert(std::is_same_v, T> || + std::is_same_v, T>, + "Only arrays should be mutable attributes"); + if (auto value = mData.Lookup(aAttrName)) { + if (value->is()) { + T& val = value->as(); + return SomeRef(val); + } + } + return Nothing(); + } + + // Get stringified value + bool GetAttribute(nsAtom* aAttrName, nsAString& aAttrValue) const; + + bool HasAttribute(nsAtom* aAttrName) const { + return mData.Contains(aAttrName); + } + + bool Remove(nsAtom* aAttrName) { return mData.Remove(aAttrName); } + + uint32_t Count() const { return mData.Count(); } + + // Update one instance with the entries in another. The supplied AccAttributes + // will be emptied. + void Update(AccAttributes* aOther); + + /** + * Return true if all the attributes in this instance are equal to all the + * attributes in another instance. + */ + bool Equal(const AccAttributes* aOther) const; + + /** + * Copy attributes from this instance to another instance. + * This should only be used in very specific cases; e.g. merging two sets of + * cached attributes without modifying the cache. It can only copy simple + * value types; e.g. it can't copy array values. Attempting to copy an + * AccAttributes with uncopyable values will cause an assertion. + */ + void CopyTo(AccAttributes* aDest) const; + + // An entry class for our iterator. + class Entry { + public: + Entry(nsAtom* aAttrName, const AttrValueType* aAttrValue) + : mName(aAttrName), mValue(aAttrValue) {} + + nsAtom* Name() const { return mName; } + + template + Maybe Value() const { + if constexpr (std::is_same_v) { + if (mValue->is>()) { + const T& val = *(mValue->as>().get()); + return SomeRef(val); + } + } else if constexpr (std::is_same_v) { + if (mValue->is>()) { + const T& val = *(mValue->as>()); + return SomeRef(val); + } + } else { + if (mValue->is()) { + const T& val = mValue->as(); + return SomeRef(val); + } + } + return Nothing(); + } + + void NameAsString(nsString& aName) const { + mName->ToString(aName); + if (StringBeginsWith(aName, u"aria-"_ns)) { + // Found 'aria-' + aName.ReplaceLiteral(0, 5, u""); + } + } + + void ValueAsString(nsAString& aValueString) const { + StringFromValueAndName(mName, *mValue, aValueString); + } + + // Size of the pair in the hash table. + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf); + + private: + nsAtom* mName; + const AttrValueType* mValue; + + friend class AccAttributes; + }; + + class Iterator { + public: + explicit Iterator(AtomVariantMap::const_iterator aIter) + : mHashIterator(aIter) {} + + Iterator() = delete; + Iterator(const Iterator&) = delete; + Iterator& operator=(const Iterator&) = delete; + + bool operator!=(const Iterator& aOther) const { + return mHashIterator != aOther.mHashIterator; + } + + Iterator& operator++() { + mHashIterator++; + return *this; + } + + Entry operator*() const { + auto& entry = *mHashIterator; + return Entry(entry.GetKey(), &entry.GetData()); + } + + private: + AtomVariantMap::const_iterator mHashIterator; + }; + + friend class Iterator; + + Iterator begin() const { return Iterator(mData.begin()); } + Iterator end() const { return Iterator(mData.end()); } + +#ifdef A11Y_LOG + static void DebugPrint(const char* aPrefix, const AccAttributes& aAttributes); +#endif + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf); + + private: + static void StringFromValueAndName(nsAtom* aAttrName, + const AttrValueType& aValue, + nsAString& aValueString); + + AtomVariantMap mData; + + friend struct IPC::ParamTraits; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp new file mode 100644 index 0000000000..1d1b4386f8 --- /dev/null +++ b/accessible/base/AccEvent.cpp @@ -0,0 +1,256 @@ +/* -*- 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 "xpcAccEvents.h" +#include "States.h" +#include "TextRange.h" +#include "xpcAccessibleDocument.h" +#include "xpcAccessibleTextRange.h" + +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/UserActivation.h" + +#include "nsComponentManagerUtils.h" +#include "nsIMutableArray.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, LocalAccessible* aAccessible, + EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) + : mEventType(aEventType), mEventRule(aEventRule), mAccessible(aAccessible) { + if (aIsFromUserInput == eAutoDetect) { + mIsFromUserInput = dom::UserActivation::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 + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// 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(LocalAccessible* 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(LocalAccessible* aTarget, bool aNeedsShutdown) + : AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget), + mNeedsShutdown(aNeedsShutdown) { + mNextSibling = mAccessible->LocalNextSibling(); + mPrevSibling = mAccessible->LocalPrevSibling(); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccShowEvent +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// AccTextSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, + int32_t aReason, + int32_t aGranularity) + : AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget, + eAutoDetect, eCoalesceTextSelChange), + mSel(aSelection), + mReason(aReason), + mGranularity(aGranularity) {} + +AccTextSelChangeEvent::~AccTextSelChangeEvent() {} + +bool AccTextSelChangeEvent::IsCaretMoveOnly() const { + return mSel->RangeCount() == 1 && mSel->IsCollapsed() && + ((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON | + nsISelectionListener::COLLAPSETOEND_REASON)) == 0); +} + +void AccTextSelChangeEvent::SelectionRanges( + nsTArray* aRanges) const { + TextRange::TextRangesFromSelection(mSel, aRanges); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccSelChangeEvent::AccSelChangeEvent(LocalAccessible* aWidget, + LocalAccessible* 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; + } +} + +already_AddRefed a11y::MakeXPCEvent(AccEvent* aEvent) { + DocAccessible* doc = aEvent->Document(); + LocalAccessible* acc = aEvent->GetAccessible(); + nsINode* node = acc->GetNode(); + 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), + node, 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), node, 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), node, + 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), node, fromUser, + cm->GetCaretOffset(), cm->IsSelectionCollapsed(), cm->IsAtEndOfLine(), + cm->GetGranularity()); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eTextSelChangeEvent)) { + AccTextSelChangeEvent* tsc = downcast_accEvent(aEvent); + AutoTArray ranges; + tsc->SelectionRanges(&ranges); + + nsCOMPtr xpcRanges = + do_CreateInstance(NS_ARRAY_CONTRACTID); + uint32_t len = ranges.Length(); + for (uint32_t idx = 0; idx < len; idx++) { + xpcRanges->AppendElement(new xpcAccessibleTextRange(ranges[idx])); + } + + xpEvent = new xpcAccTextSelectionChangeEvent( + type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, xpcRanges); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eObjectAttrChangedEvent)) { + AccObjectAttrChangedEvent* oac = downcast_accEvent(aEvent); + nsString attribute; + oac->GetAttribute()->ToString(attribute); + xpEvent = new xpcAccObjectAttributeChangedEvent( + type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, attribute); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eScrollingEvent)) { + AccScrollingEvent* sa = downcast_accEvent(aEvent); + xpEvent = new xpcAccScrollingEvent( + type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, sa->ScrollX(), + sa->ScrollY(), sa->MaxScrollX(), sa->MaxScrollY()); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eAnnouncementEvent)) { + AccAnnouncementEvent* aa = downcast_accEvent(aEvent); + xpEvent = new xpcAccAnnouncementEvent(type, ToXPC(acc), ToXPCDocument(doc), + node, fromUser, aa->Announcement(), + aa->Priority()); + return xpEvent.forget(); + } + + xpEvent = + new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), node, fromUser); + return xpEvent.forget(); +} diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h new file mode 100644 index 0000000000..a4ff82916a --- /dev/null +++ b/accessible/base/AccEvent.h @@ -0,0 +1,562 @@ +/* -*- 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/LocalAccessible.h" + +class nsEventShell; +namespace mozilla { + +namespace dom { +class Selection; +} + +namespace a11y { + +class DocAccessible; +class EventQueue; +class TextRange; + +// 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, LocalAccessible* 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); + } + + LocalAccessible* 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, + eObjectAttrChangedEvent, + eScrollingEvent, + eAnnouncementEvent, + }; + + 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(LocalAccessible* aAccessible, uint64_t aState, + bool aIsEnabled, + EIsFromUserInput aIsFromUserInput = eAutoDetect) + : AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, + aIsFromUserInput, eCoalesceStateChange), + mState(aState), + mIsEnabled(aIsEnabled) {} + + AccStateChangeEvent(LocalAccessible* 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(LocalAccessible* 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, LocalAccessible* 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, LocalAccessible* aTarget) + : AccTreeMutationEvent(aEventType, aTarget) { + // Don't coalesce these since they are coalesced by reorder event. Coalesce + // contained text change events. + mParent = mAccessible->LocalParent(); + } + 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; } + + LocalAccessible* LocalParent() const { return mParent; } + + protected: + RefPtr mParent; + RefPtr mTextChangeEvent; + + friend class EventTree; + friend class NotificationController; +}; + +/** + * Accessible hide event. + */ +class AccHideEvent : public AccMutationEvent { + public: + explicit AccHideEvent(LocalAccessible* aTarget, bool aNeedsShutdown = true); + + // Event + static const EventGroup kEventGroup = eHideEvent; + virtual unsigned int GetEventGroups() const override { + return AccMutationEvent::GetEventGroups() | (1U << eHideEvent); + } + + // AccHideEvent + LocalAccessible* TargetParent() const { return mParent; } + LocalAccessible* TargetNextSibling() const { return mNextSibling; } + LocalAccessible* 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(LocalAccessible* aTarget) + : AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget) {} + + // Event + static const EventGroup kEventGroup = eShowEvent; + virtual unsigned int GetEventGroups() const override { + return AccMutationEvent::GetEventGroups() | (1U << eShowEvent); + } +}; + +/** + * Class for reorder accessible event. Takes care about + */ +class AccReorderEvent : public AccTreeMutationEvent { + public: + explicit AccReorderEvent(LocalAccessible* 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); + } + + /* + * Make this an inner reorder event that is coalesced into + * a reorder event of an ancestor. + */ + void SetInner() { mEventType = ::nsIAccessibleEvent::EVENT_INNER_REORDER; } +}; + +/** + * Accessible caret move event. + */ +class AccCaretMoveEvent : public AccEvent { + public: + AccCaretMoveEvent(LocalAccessible* aAccessible, int32_t aCaretOffset, + bool aIsSelectionCollapsed, bool aIsAtEndOfLine, + int32_t aGranularity, + EIsFromUserInput aIsFromUserInput = eAutoDetect) + : AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible, + aIsFromUserInput), + mCaretOffset(aCaretOffset), + mIsSelectionCollapsed(aIsSelectionCollapsed), + mIsAtEndOfLine(aIsAtEndOfLine), + mGranularity(aGranularity) {} + 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; } + + bool IsSelectionCollapsed() const { return mIsSelectionCollapsed; } + bool IsAtEndOfLine() { return mIsAtEndOfLine; } + + int32_t GetGranularity() const { return mGranularity; } + + private: + int32_t mCaretOffset; + + bool mIsSelectionCollapsed; + bool mIsAtEndOfLine; + int32_t mGranularity; +}; + +/** + * Accessible text selection change event. + */ +class AccTextSelChangeEvent : public AccEvent { + public: + AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, int32_t aReason, + int32_t aGranularity); + 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; + + int32_t GetGranularity() const { return mGranularity; } + + /** + * Return selection ranges in document/control. + */ + void SelectionRanges(nsTArray* aRanges) const; + + private: + RefPtr mSel; + int32_t mReason; + int32_t mGranularity; + + friend class EventQueue; + friend class SelectionManager; +}; + +/** + * Accessible widget selection change event. + */ +class AccSelChangeEvent : public AccEvent { + public: + enum SelChangeType { eSelectionAdd, eSelectionRemove }; + + AccSelChangeEvent(LocalAccessible* aWidget, LocalAccessible* aItem, + SelChangeType aSelChangeType); + + virtual ~AccSelChangeEvent() {} + + // AccEvent + static const EventGroup kEventGroup = eSelectionChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent); + } + + // AccSelChangeEvent + LocalAccessible* Widget() const { return mWidget; } + + private: + RefPtr mWidget; + RefPtr mItem; + SelChangeType mSelChangeType; + uint32_t mPreceedingCount; + AccSelChangeEvent* mPackedEvent; + + friend class EventQueue; +}; + +/** + * Accessible object attribute changed event. + */ +class AccObjectAttrChangedEvent : public AccEvent { + public: + AccObjectAttrChangedEvent(LocalAccessible* aAccessible, nsAtom* 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 + nsAtom* GetAttribute() const { return mAttribute; } + + private: + RefPtr mAttribute; + + virtual ~AccObjectAttrChangedEvent() {} +}; + +/** + * Accessible scroll event. + */ +class AccScrollingEvent : public AccEvent { + public: + AccScrollingEvent(uint32_t aEventType, LocalAccessible* aAccessible, + uint32_t aScrollX, uint32_t aScrollY, uint32_t aMaxScrollX, + uint32_t aMaxScrollY) + : AccEvent(aEventType, aAccessible), + mScrollX(aScrollX), + mScrollY(aScrollY), + mMaxScrollX(aMaxScrollX), + mMaxScrollY(aMaxScrollY) {} + + virtual ~AccScrollingEvent() {} + + // AccEvent + static const EventGroup kEventGroup = eScrollingEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eScrollingEvent); + } + + // The X scrolling offset of the container when the event was fired. + uint32_t ScrollX() { return mScrollX; } + // The Y scrolling offset of the container when the event was fired. + uint32_t ScrollY() { return mScrollY; } + // The max X offset of the container. + uint32_t MaxScrollX() { return mMaxScrollX; } + // The max Y offset of the container. + uint32_t MaxScrollY() { return mMaxScrollY; } + + private: + uint32_t mScrollX; + uint32_t mScrollY; + uint32_t mMaxScrollX; + uint32_t mMaxScrollY; +}; + +/** + * Accessible announcement event. + */ +class AccAnnouncementEvent : public AccEvent { + public: + AccAnnouncementEvent(LocalAccessible* aAccessible, + const nsAString& aAnnouncement, uint16_t aPriority) + : AccEvent(nsIAccessibleEvent::EVENT_ANNOUNCEMENT, aAccessible), + mAnnouncement(aAnnouncement), + mPriority(aPriority) {} + + virtual ~AccAnnouncementEvent() {} + + // AccEvent + static const EventGroup kEventGroup = eAnnouncementEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eAnnouncementEvent); + } + + const nsString& Announcement() const { return mAnnouncement; } + + uint16_t Priority() { return mPriority; } + + private: + nsString mAnnouncement; + uint16_t mPriority; +}; + +/** + * 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 0000000000..3b536b1aa4 --- /dev/null +++ b/accessible/base/AccGroupInfo.cpp @@ -0,0 +1,397 @@ +/* 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 "mozilla/a11y/Accessible.h" +#include "mozilla/a11y/TableAccessible.h" + +#include "nsAccUtils.h" +#include "nsIAccessiblePivot.h" + +#include "Pivot.h" +#include "States.h" + +using namespace mozilla::a11y; + +static role BaseRole(role aRole); + +// This rule finds candidate siblings for compound widget children. +class CompoundWidgetSiblingRule : public PivotRule { + public: + CompoundWidgetSiblingRule() = delete; + explicit CompoundWidgetSiblingRule(role aRole) : mRole(aRole) {} + + uint16_t Match(Accessible* aAcc) override { + // If the acc has a matching role, that's a valid sibling. If the acc is + // separator then the group is ended. Return a match for separators with + // the assumption that the caller will check for the role of the returned + // accessible. + const role accRole = aAcc->Role(); + if (BaseRole(accRole) == mRole || accRole == role::SEPARATOR) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + // Ignore generic accessibles, but keep searching through the subtree for + // siblings. + if (aAcc->IsGeneric()) { + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + private: + role mRole = role::NOTHING; +}; + +AccGroupInfo::AccGroupInfo(const Accessible* aItem, role aRole) + : mPosInSet(0), mSetSize(0), mParentId(0), mItem(aItem), mRole(aRole) { + MOZ_COUNT_CTOR(AccGroupInfo); + Update(); +} + +void AccGroupInfo::Update() { + mParentId = 0; + + Accessible* parent = mItem->GetNonGenericParent(); + if (!parent) { + return; + } + + const int32_t level = GetARIAOrDefaultLevel(mItem); + + // Compute position in set. + mPosInSet = 1; + + // Search backwards through the tree for candidate siblings. + Accessible* candidateSibling = const_cast(mItem); + Pivot pivot{parent}; + CompoundWidgetSiblingRule widgetSiblingRule{mRole}; + while ((candidateSibling = pivot.Prev(candidateSibling, widgetSiblingRule)) && + candidateSibling != parent) { + // If the sibling is separator then the group is ended. + if (candidateSibling->Role() == roles::SEPARATOR) { + break; + } + + const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo(); + // Skip invisible siblings. + // If the sibling has calculated group info, that means it's visible. + if (!siblingGroupInfo && candidateSibling->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). + const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling); + if (siblingLevel < level) { + mParentId = candidateSibling->ID(); + 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 (siblingGroupInfo) { + mPosInSet += siblingGroupInfo->mPosInSet; + mParentId = siblingGroupInfo->mParentId; + mSetSize = siblingGroupInfo->mSetSize; + return; + } + + mPosInSet++; + } + + // Compute set size. + mSetSize = mPosInSet; + + candidateSibling = const_cast(mItem); + while ((candidateSibling = pivot.Next(candidateSibling, widgetSiblingRule)) && + candidateSibling != parent) { + // If the sibling is separator then the group is ended. + if (candidateSibling->Role() == roles::SEPARATOR) { + break; + } + + const AccGroupInfo* siblingGroupInfo = candidateSibling->GetGroupInfo(); + // Skip invisible siblings. + // If the sibling has calculated group info, that means it's visible. + if (!siblingGroupInfo && candidateSibling->State() & states::INVISIBLE) { + continue; + } + + // and check if it's hierarchical flatten structure. + const int32_t siblingLevel = GetARIAOrDefaultLevel(candidateSibling); + 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 (siblingGroupInfo) { + mParentId = siblingGroupInfo->mParentId; + mSetSize = siblingGroupInfo->mSetSize; + return; + } + + mSetSize++; + } + + if (mParentId) { + return; + } + + roles::Role parentRole = parent->Role(); + if (ShouldReportRelations(mRole, parentRole)) { + mParentId = parent->ID(); + } + + // 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) { + // Find the relevant grandparent of the item. Use that parent as the root + // and find the previous outline item sibling within that root. + Accessible* grandParent = parent->GetNonGenericParent(); + MOZ_ASSERT(grandParent); + Pivot pivot{grandParent}; + CompoundWidgetSiblingRule parentSiblingRule{mRole}; + Accessible* parentPrevSibling = pivot.Prev(parent, widgetSiblingRule); + if (parentPrevSibling && parentPrevSibling->Role() == mRole) { + mParentId = parentPrevSibling->ID(); + 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->GetNonGenericParent(); + if (grandParent && grandParent->Role() == mRole) { + mParentId = grandParent->ID(); + } + } +} + +AccGroupInfo* AccGroupInfo::CreateGroupInfo(const 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 && + role != mozilla::a11y::roles::COMMENT) { + return nullptr; + } + + AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role)); + return info; +} + +Accessible* AccGroupInfo::FirstItemOf(const 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->GetOrCreateGroupInfo(); + 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->GetOrCreateGroupInfo(); + 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; +} + +uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer, + bool* aIsHierarchical) { + uint32_t itemCount = 0; + switch (aContainer->Role()) { + case roles::TABLE: + if (auto val = aContainer->GetIntARIAAttr(nsGkAtoms::aria_rowcount)) { + if (*val >= 0) { + return *val; + } + } + if (TableAccessible* tableAcc = aContainer->AsTable()) { + return tableAcc->RowCount(); + } + break; + case roles::ROW: + if (Accessible* table = nsAccUtils::TableFor(aContainer)) { + if (auto val = table->GetIntARIAAttr(nsGkAtoms::aria_colcount)) { + if (*val >= 0) { + return *val; + } + } + if (TableAccessible* tableAcc = table->AsTable()) { + return tableAcc->ColCount(); + } + } + break; + case roles::OUTLINE: + case roles::LIST: + case roles::MENUBAR: + case roles::MENUPOPUP: + case roles::COMBOBOX: + case roles::GROUPING: + case roles::TREE_TABLE: + case roles::COMBOBOX_LIST: + case roles::LISTBOX: + case roles::DEFINITION_LIST: + case roles::EDITCOMBOBOX: + case roles::RADIO_GROUP: + case roles::PAGETABLIST: { + Accessible* childItem = AccGroupInfo::FirstItemOf(aContainer); + if (!childItem) { + childItem = aContainer->FirstChild(); + if (childItem && childItem->IsTextLeaf()) { + // First child can be a text leaf, check its sibling for an item. + childItem = childItem->NextSibling(); + } + } + + if (childItem) { + GroupPos groupPos = childItem->GroupPosition(); + itemCount = groupPos.setSize; + if (groupPos.level && aIsHierarchical) { + *aIsHierarchical = true; + } + } + break; + } + default: + break; + } + + return itemCount; +} + +Accessible* AccGroupInfo::NextItemTo(Accessible* aItem) { + AccGroupInfo* groupInfo = aItem->GetOrCreateGroupInfo(); + 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->ChildAt(idx); + AccGroupInfo* nextGroupInfo = nextItem->GetOrCreateGroupInfo(); + if (nextGroupInfo && + nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { + return nextItem; + } + } + + MOZ_ASSERT_UNREACHABLE( + "Item in the middle of the group but there's no next item!"); + return nullptr; +} + +size_t AccGroupInfo::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + // We don't count mParentId or mItem since they (should be) counted + // as part of the document. + return aMallocSizeOf(this); +} + +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; +} + +int32_t AccGroupInfo::GetARIAOrDefaultLevel(const Accessible* aAccessible) { + int32_t level = 0; + aAccessible->ARIAGroupPosition(&level, nullptr, nullptr); + + if (level != 0) return level; + + return aAccessible->GetLevel(true); +} + +Accessible* AccGroupInfo::ConceptualParent() const { + if (!mParentId) { + // The conceptual parent can never be the document, so id 0 means none. + return nullptr; + } + if (Accessible* doc = + nsAccUtils::DocumentFor(const_cast(mItem))) { + return nsAccUtils::GetAccessibleByID(doc, mParentId); + } + return nullptr; +} + +static role BaseRole(role aRole) { + if (aRole == roles::CHECK_MENU_ITEM || aRole == roles::PARENT_MENUITEM || + aRole == roles::RADIO_MENU_ITEM) { + return roles::MENUITEM; + } + + if (aRole == roles::CHECK_RICH_OPTION) { + return roles::RICH_OPTION; + } + + return aRole; +} diff --git a/accessible/base/AccGroupInfo.h b/accessible/base/AccGroupInfo.h new file mode 100644 index 0000000000..a9afa14b8e --- /dev/null +++ b/accessible/base/AccGroupInfo.h @@ -0,0 +1,101 @@ +/* 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 "nsISupportsImpl.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/a11y/Role.h" + +namespace mozilla { +namespace a11y { + +class Accessible; + +/** + * Calculate and store group information. + */ +class AccGroupInfo { + public: + MOZ_COUNTED_DTOR(AccGroupInfo) + + AccGroupInfo() = default; + AccGroupInfo(AccGroupInfo&&) = default; + AccGroupInfo& operator=(AccGroupInfo&&) = default; + + /** + * 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; + + /** + * Update group information. + */ + void Update(); + + /** + * Create group info. + */ + static AccGroupInfo* CreateGroupInfo(const Accessible* aAccessible); + + /** + * Return a first item for the given container. + */ + static Accessible* FirstItemOf(const Accessible* aContainer); + + /** + * Return total number of items in container, and if it is has nested + * collections. + */ + static uint32_t TotalItemCount(Accessible* aContainer, bool* aIsHierarchical); + + /** + * Return next item of the same group to the given item. + */ + static Accessible* NextItemTo(Accessible* aItem); + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf); + + protected: + AccGroupInfo(const Accessible* aItem, a11y::role aRole); + + private: + AccGroupInfo(const AccGroupInfo&) = delete; + AccGroupInfo& operator=(const AccGroupInfo&) = delete; + + /** + * Return true if the given parent and child roles should have their node + * relations reported. + */ + static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole); + + /** + * Return ARIA level value or the default one if ARIA is missed for the + * given accessible. + */ + static int32_t GetARIAOrDefaultLevel(const Accessible* aAccessible); + + uint32_t mPosInSet; + uint32_t mSetSize; + uint64_t mParentId; + const Accessible* mItem; + a11y::role mRole; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp new file mode 100644 index 0000000000..badd34c0d5 --- /dev/null +++ b/accessible/base/AccIterator.cpp @@ -0,0 +1,360 @@ +/* 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" +#include "DocAccessible-inl.h" +#include "LocalAccessible-inl.h" +#include "XULTreeAccessible.h" + +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/dom/DocumentOrShadowRoot.h" +#include "mozilla/dom/HTMLLabelElement.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// AccIterator +//////////////////////////////////////////////////////////////////////////////// + +AccIterator::AccIterator(const LocalAccessible* aAccessible, + filters::FilterFuncPtr aFilterFunc) + : mFilterFunc(aFilterFunc) { + mState = new IteratorState(aAccessible); +} + +AccIterator::~AccIterator() { + while (mState) { + IteratorState* tmp = mState; + mState = tmp->mParentState; + delete tmp; + } +} + +LocalAccessible* AccIterator::Next() { + while (mState) { + LocalAccessible* child = mState->mParent->LocalChildAt(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(const LocalAccessible* aParent, + IteratorState* mParentState) + : mParent(aParent), mIndex(0), mParentState(mParentState) {} + +//////////////////////////////////////////////////////////////////////////////// +// RelatedAccIterator +//////////////////////////////////////////////////////////////////////////////// + +RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument, + nsIContent* aDependentContent, + nsAtom* aRelAttr) + : mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0) { + nsAutoString id; + if (aDependentContent->IsElement() && + aDependentContent->AsElement()->GetAttr(nsGkAtoms::id, id)) { + mProviders = mDocument->GetRelProviders(aDependentContent->AsElement(), id); + } +} + +LocalAccessible* RelatedAccIterator::Next() { + if (!mProviders) return nullptr; + + while (mIndex < mProviders->Length()) { + const auto& provider = (*mProviders)[mIndex++]; + + // Return related accessible for the given attribute. + if (provider->mRelAttr == mRelAttr) { + LocalAccessible* 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 LocalAccessible* aAccessible, + LabelFilter aFilter) + : mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for), + mAcc(aAccessible), + mLabelFilter(aFilter) {} + +bool HTMLLabelIterator::IsLabel(LocalAccessible* aLabel) { + dom::HTMLLabelElement* labelEl = + dom::HTMLLabelElement::FromNode(aLabel->GetContent()); + return labelEl && labelEl->GetControl() == mAcc->GetContent(); +} + +LocalAccessible* HTMLLabelIterator::Next() { + // Get either