From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- accessible/base/ARIAMap.cpp | 1516 ++++++++++++++++++++++ accessible/base/ARIAMap.h | 305 +++++ accessible/base/ARIAStateMap.cpp | 330 +++++ accessible/base/ARIAStateMap.h | 66 + accessible/base/AccEvent.cpp | 305 +++++ accessible/base/AccEvent.h | 617 +++++++++ accessible/base/AccGroupInfo.cpp | 288 +++++ accessible/base/AccGroupInfo.h | 118 ++ accessible/base/AccIterator.cpp | 338 +++++ accessible/base/AccIterator.h | 303 +++++ accessible/base/AccTypes.h | 97 ++ accessible/base/AccessibleOrProxy.cpp | 97 ++ accessible/base/AccessibleOrProxy.h | 180 +++ accessible/base/Asserts.cpp | 29 + accessible/base/DocManager.cpp | 562 +++++++++ accessible/base/DocManager.h | 189 +++ accessible/base/EmbeddedObjCollector.cpp | 63 + accessible/base/EmbeddedObjCollector.h | 68 + accessible/base/EventQueue.cpp | 335 +++++ accessible/base/EventQueue.h | 76 ++ accessible/base/EventTree.cpp | 601 +++++++++ accessible/base/EventTree.h | 128 ++ accessible/base/Filters.cpp | 44 + accessible/base/Filters.h | 46 + accessible/base/FocusManager.cpp | 414 ++++++ accessible/base/FocusManager.h | 146 +++ accessible/base/IDSet.h | 129 ++ accessible/base/Logging.cpp | 925 ++++++++++++++ accessible/base/Logging.h | 230 ++++ accessible/base/MarkupMap.h | 561 +++++++++ accessible/base/NotificationController.cpp | 974 ++++++++++++++ accessible/base/NotificationController.h | 462 +++++++ accessible/base/Pivot.cpp | 596 +++++++++ accessible/base/Pivot.h | 108 ++ accessible/base/Platform.h | 160 +++ accessible/base/Relation.h | 98 ++ accessible/base/RelationType.h | 163 +++ accessible/base/RelationTypeMap.h | 87 ++ accessible/base/Role.h | 1090 ++++++++++++++++ accessible/base/RoleMap.h | 1879 ++++++++++++++++++++++++++++ accessible/base/SelectionManager.cpp | 207 +++ accessible/base/SelectionManager.h | 130 ++ accessible/base/States.h | 297 +++++ accessible/base/Statistics.h | 42 + accessible/base/StyleInfo.cpp | 107 ++ accessible/base/StyleInfo.h | 50 + accessible/base/TextAttrs.cpp | 750 +++++++++++ accessible/base/TextAttrs.h | 433 +++++++ accessible/base/TextRange-inl.h | 26 + accessible/base/TextRange.cpp | 518 ++++++++ accessible/base/TextRange.h | 283 +++++ accessible/base/TextUpdater.cpp | 189 +++ accessible/base/TextUpdater.h | 95 ++ accessible/base/TreeWalker.cpp | 348 ++++++ accessible/base/TreeWalker.h | 142 +++ accessible/base/XULMap.h | 126 ++ accessible/base/moz.build | 117 ++ accessible/base/nsAccCache.h | 24 + accessible/base/nsAccUtils.cpp | 519 ++++++++ accessible/base/nsAccUtils.h | 270 ++++ accessible/base/nsAccessibilityService.cpp | 1768 ++++++++++++++++++++++++++ accessible/base/nsAccessibilityService.h | 507 ++++++++ accessible/base/nsAccessiblePivot.cpp | 522 ++++++++ accessible/base/nsAccessiblePivot.h | 138 ++ accessible/base/nsCoreUtils.cpp | 587 +++++++++ accessible/base/nsCoreUtils.h | 328 +++++ accessible/base/nsEventShell.cpp | 71 ++ accessible/base/nsEventShell.h | 66 + accessible/base/nsTextEquivUtils.cpp | 355 ++++++ accessible/base/nsTextEquivUtils.h | 164 +++ 70 files changed, 23902 insertions(+) create mode 100644 accessible/base/ARIAMap.cpp create mode 100644 accessible/base/ARIAMap.h create mode 100644 accessible/base/ARIAStateMap.cpp create mode 100644 accessible/base/ARIAStateMap.h create mode 100644 accessible/base/AccEvent.cpp create mode 100644 accessible/base/AccEvent.h create mode 100644 accessible/base/AccGroupInfo.cpp create mode 100644 accessible/base/AccGroupInfo.h create mode 100644 accessible/base/AccIterator.cpp create mode 100644 accessible/base/AccIterator.h create mode 100644 accessible/base/AccTypes.h create mode 100644 accessible/base/AccessibleOrProxy.cpp create mode 100644 accessible/base/AccessibleOrProxy.h create mode 100644 accessible/base/Asserts.cpp create mode 100644 accessible/base/DocManager.cpp create mode 100644 accessible/base/DocManager.h create mode 100644 accessible/base/EmbeddedObjCollector.cpp create mode 100644 accessible/base/EmbeddedObjCollector.h create mode 100644 accessible/base/EventQueue.cpp create mode 100644 accessible/base/EventQueue.h create mode 100644 accessible/base/EventTree.cpp create mode 100644 accessible/base/EventTree.h create mode 100644 accessible/base/Filters.cpp create mode 100644 accessible/base/Filters.h create mode 100644 accessible/base/FocusManager.cpp create mode 100644 accessible/base/FocusManager.h create mode 100644 accessible/base/IDSet.h create mode 100644 accessible/base/Logging.cpp create mode 100644 accessible/base/Logging.h create mode 100644 accessible/base/MarkupMap.h create mode 100644 accessible/base/NotificationController.cpp create mode 100644 accessible/base/NotificationController.h create mode 100644 accessible/base/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/RelationType.h create mode 100644 accessible/base/RelationTypeMap.h create mode 100644 accessible/base/Role.h create mode 100644 accessible/base/RoleMap.h create mode 100644 accessible/base/SelectionManager.cpp create mode 100644 accessible/base/SelectionManager.h create mode 100644 accessible/base/States.h create mode 100644 accessible/base/Statistics.h create mode 100644 accessible/base/StyleInfo.cpp create mode 100644 accessible/base/StyleInfo.h create mode 100644 accessible/base/TextAttrs.cpp create mode 100644 accessible/base/TextAttrs.h create mode 100644 accessible/base/TextRange-inl.h create mode 100644 accessible/base/TextRange.cpp create mode 100644 accessible/base/TextRange.h create mode 100644 accessible/base/TextUpdater.cpp create mode 100644 accessible/base/TextUpdater.h create mode 100644 accessible/base/TreeWalker.cpp create mode 100644 accessible/base/TreeWalker.h create mode 100644 accessible/base/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/nsAccessiblePivot.cpp create mode 100644 accessible/base/nsAccessiblePivot.h create mode 100644 accessible/base/nsCoreUtils.cpp create mode 100644 accessible/base/nsCoreUtils.h create mode 100644 accessible/base/nsEventShell.cpp create mode 100644 accessible/base/nsEventShell.h create mode 100644 accessible/base/nsTextEquivUtils.cpp create mode 100644 accessible/base/nsTextEquivUtils.h (limited to 'accessible/base') diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp new file mode 100644 index 0000000000..113460269e --- /dev/null +++ b/accessible/base/ARIAMap.cpp @@ -0,0 +1,1516 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ARIAMap.h" + +#include "nsAccUtils.h" +#include "nsCoreUtils.h" +#include "Role.h" +#include "States.h" + +#include "nsAttrName.h" +#include "nsWhitespaceTokenizer.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; +using namespace mozilla::a11y::aria; + +static const uint32_t kGenericAccType = 0; + +/** + * This list of WAI-defined roles are currently hardcoded. + * Eventually we will most likely be loading an RDF resource that contains this + * information Using RDF will also allow for role extensibility. See bug 280138. + * + * Definition of nsRoleMapEntry contains comments explaining this table. + * + * When no Role enum mapping exists for an ARIA role, the role will be exposed + * via the object attribute "xml-roles". + */ + +static const nsRoleMapEntry sWAIRoleMaps[] = { + // 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 + }, + { // 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 + }, + { // 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 + }, + { // 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 + }, + { // 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 + }, + { // 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 + }, + { // suggestion + nsGkAtoms::suggestion, + roles::SUGGESTION, + 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 + }, + { // 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_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_current, ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL }, + {nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_details, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, + {nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN | ATTR_GLOBAL }, + {nsGkAtoms::aria_errormessage, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_grabbed, ATTR_VALTOKEN | ATTR_GLOBAL }, + {nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ_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_BYPASSOBJ | ATTR_GLOBAL }, + {nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, + {nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */ + {nsGkAtoms::aria_sort, ATTR_VALTOKEN }, + {nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ }, + {nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ }, + {nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ }, + {nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ } + // clang-format on +}; + +namespace { + +struct RoleComparator { + const nsDependentSubstring& mRole; + explicit RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {} + int operator()(const nsRoleMapEntry& aEntry) const { + return Compare(mRole, aEntry.ARIARoleString()); + } +}; + +} // namespace + +const nsRoleMapEntry* aria::GetRoleMap(dom::Element* aEl) { + return GetRoleMapFromIndex(GetRoleMapIndex(aEl)); +} + +uint8_t aria::GetRoleMapIndex(dom::Element* aEl) { + nsAutoString roles; + if (!aEl || !aEl->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roles) || + roles.IsEmpty()) { + // We treat role="" as if the role attribute is absent (per aria spec:8.1.1) + return NO_ROLE_MAP_ENTRY_INDEX; + } + + nsWhitespaceTokenizer tokenizer(roles); + while (tokenizer.hasMoreTokens()) { + // Do a binary search through table for the next role in role list + const nsDependentSubstring role = tokenizer.nextToken(); + size_t idx; + if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps), + RoleComparator(role), &idx)) { + return idx; + } + } + + // Always use some entry index if there is a non-empty role string + // To ensure an accessible object is created + return LANDMARK_ROLE_MAP_ENTRY_INDEX; +} + +const nsRoleMapEntry* aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex) { + switch (aRoleMapIndex) { + case NO_ROLE_MAP_ENTRY_INDEX: + return nullptr; + case EMPTY_ROLE_MAP_ENTRY_INDEX: + return &gEmptyRoleMap; + case LANDMARK_ROLE_MAP_ENTRY_INDEX: + return &sLandmarkRoleMap; + default: + return sWAIRoleMaps + aRoleMapIndex; + } +} + +uint8_t aria::GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMapEntry) { + if (aRoleMapEntry == nullptr) { + return NO_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &gEmptyRoleMap) { + return EMPTY_ROLE_MAP_ENTRY_INDEX; + } else if (aRoleMapEntry == &sLandmarkRoleMap) { + return LANDMARK_ROLE_MAP_ENTRY_INDEX; + } else { + return aRoleMapEntry - sWAIRoleMaps; + } +} + +uint64_t aria::UniversalStatesFor(mozilla::dom::Element* aElement) { + uint64_t state = 0; + uint32_t index = 0; + while (MapToState(sWAIUnivStateMap[index], aElement, &state)) index++; + + return state; +} + +uint8_t aria::AttrCharacteristicsFor(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() && + aContent->AsElement()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::aria_hidden, + nsGkAtoms::_true, eCaseMatters); +} + +//////////////////////////////////////////////////////////////////////////////// +// AttrIterator class + +AttrIterator::AttrIterator(nsIContent* aContent) + : mElement(dom::Element::FromNode(aContent)), mAttrIdx(0) { + mAttrCount = mElement ? mElement->GetAttrCount() : 0; +} + +bool AttrIterator::Next(nsAString& aAttrName, nsAString& aAttrValue) { + while (mAttrIdx < mAttrCount) { + const nsAttrName* attr = mElement->GetAttrNameAt(mAttrIdx); + mAttrIdx++; + if (attr->NamespaceEquals(kNameSpaceID_None)) { + nsAtom* attrAtom = attr->Atom(); + nsDependentAtomString attrStr(attrAtom); + if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // Not ARIA + + uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom); + if (attrFlags & ATTR_BYPASSOBJ) + continue; // No need to handle exposing as obj attribute here + + if ((attrFlags & ATTR_VALTOKEN) && + !nsAccUtils::HasDefinedARIAToken(mElement, attrAtom)) + continue; // only expose token based attributes if they are defined + + if ((attrFlags & ATTR_BYPASSOBJ_IF_FALSE) && + mElement->AttrValueIs(kNameSpaceID_None, attrAtom, nsGkAtoms::_false, + eCaseMatters)) { + continue; // only expose token based attribute if value is not 'false'. + } + + nsAutoString value; + if (mElement->GetAttr(kNameSpaceID_None, attrAtom, value)) { + aAttrName.Assign(Substring(attrStr, 5)); + if (attrFlags & ATTR_VALTOKEN) { + nsAtom* normalizedValue = + nsAccUtils::NormalizeARIAToken(mElement, attrAtom); + if (normalizedValue) { + nsDependentAtomString normalizedValueStr(normalizedValue); + aAttrValue.Assign(normalizedValueStr); + return true; + } + } + aAttrValue.Assign(value); + return true; + } + } + } + + return false; +} diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h new file mode 100644 index 0000000000..b45f2202d2 --- /dev/null +++ b/accessible/base/ARIAMap.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_aria_ARIAMap_h_ +#define mozilla_a11y_aria_ARIAMap_h_ + +#include "ARIAStateMap.h" +#include "mozilla/a11y/AccTypes.h" +#include "mozilla/a11y/Role.h" + +#include "nsAtom.h" +#include "nsIContent.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; + +//////////////////////////////////////////////////////////////////////////////// +// 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; + + // Accessible types this role belongs to. + uint32_t accTypes; + + // Automatic state mapping rule: always include in states + uint64_t state; // or kNoReqStates if no default state for this role + + // ARIA properties supported for this role (in other words, the aria-foo + // attribute to accessible states mapping rules). + // Currently you cannot have unlimited mappings, because + // a variable sized array would not allow the use of + // C++'s struct initialization feature. + mozilla::a11y::aria::EStateRule attributeMap1; + mozilla::a11y::aria::EStateRule attributeMap2; + mozilla::a11y::aria::EStateRule attributeMap3; + mozilla::a11y::aria::EStateRule attributeMap4; +}; + +//////////////////////////////////////////////////////////////////////////////// +// ARIA map + +/** + * These provide the mappings for WAI-ARIA roles, states and properties using + * the structs defined in this file and ARIAStateMap files. + */ +namespace mozilla { +namespace a11y { +namespace aria { + +/** + * Empty role map entry. Used by accessibility service to create an accessible + * if the accessible can't use role of used accessible class. For example, + * it is used for table cells that aren't contained by table. + */ +extern nsRoleMapEntry gEmptyRoleMap; + +/** + * Constants for the role map entry index to indicate that the role map entry + * isn't in sWAIRoleMaps, but rather is a special entry: nullptr, + * gEmptyRoleMap, and sLandmarkRoleMap + */ +const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2; +const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1; +const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX; + +/** + * Get the role map entry for a given DOM node. This will use the first + * ARIA role if the role attribute provides a space delimited list of roles. + * + * @param aEl [in] the DOM node to get the role map entry for + * @return a pointer to the role map entry for the ARIA role, or nullptr + * if none + */ +const nsRoleMapEntry* GetRoleMap(dom::Element* aEl); + +/** + * Get the role map entry pointer's index for a given DOM node. This will use + * the first ARIA role if the role attribute provides a space delimited list of + * roles. + * + * @param aEl [in] the DOM node to get the role map entry for + * @return the index of the pointer to the role map entry for the ARIA + * role, or NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetRoleMapIndex(dom::Element* aEl); + +/** + * Get the role map entry pointer for a given role map entry index. + * + * @param aRoleMapIndex [in] the role map index to get the role map entry + * pointer for + * @return a pointer to the role map entry for the ARIA role, + * or nullptr, if none + */ +const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex); + +/** + * Get the role map entry index for a given role map entry pointer. If the role + * map entry is within sWAIRoleMaps, return the index within that array, + * otherwise return one of the special index constants listed above. + * + * @param aRoleMap [in] the role map entry pointer to get the index for + * @return the index of the pointer to the role map entry, or + * NO_ROLE_MAP_ENTRY_INDEX if none + */ +uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap); + +/** + * Return accessible state from ARIA universal states applied to the given + * element. + */ +uint64_t UniversalStatesFor(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(nsAString& aAttrName, nsAString& aAttrValue); + + private: + AttrIterator() = delete; + AttrIterator(const AttrIterator&) = delete; + AttrIterator& operator=(const AttrIterator&) = delete; + + dom::Element* mElement; + uint32_t mAttrIdx; + uint32_t mAttrCount; +}; + +} // namespace aria +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp new file mode 100644 index 0000000000..0a2f256037 --- /dev/null +++ b/accessible/base/ARIAStateMap.cpp @@ -0,0 +1,330 @@ +/* -*- 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 (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) && + !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext)) + *aState |= states::MIXED; + + return true; + } + + case eFocusableUntilDisabled: { + if (!nsAccUtils::HasDefinedARIAToken(aElement, + nsGkAtoms::aria_disabled) || + aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, + nsGkAtoms::_false, eCaseMatters)) + *aState |= states::FOCUSABLE; + + return true; + } + + default: + return false; + } +} + +static void MapEnumType(dom::Element* aElement, uint64_t* aState, + const EnumTypeData& aData) { + switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName, + aData.mValues, eCaseMatters)) { + case 0: + *aState = (*aState & ~aData.mClearState) | aData.mStates[0]; + return; + case 1: + *aState = (*aState & ~aData.mClearState) | aData.mStates[1]; + return; + case 2: + *aState = (*aState & ~aData.mClearState) | aData.mStates[2]; + return; + } +} + +static void MapTokenType(dom::Element* aElement, uint64_t* aState, + const TokenTypeData& aData) { + if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) { + if (aElement->AttrValueIs(kNameSpaceID_None, 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 (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName, + nsGkAtoms::_false, eCaseMatters)) { + *aState |= aData.mPermanentState | aData.mFalseState; + return; + } + + *aState |= aData.mPermanentState | aData.mTrueState; + return; + } + + if (aData.mType & eDefinedIfAbsent) + *aState |= aData.mPermanentState | aData.mFalseState; +} diff --git a/accessible/base/ARIAStateMap.h b/accessible/base/ARIAStateMap.h new file mode 100644 index 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/AccEvent.cpp b/accessible/base/AccEvent.cpp new file mode 100644 index 0000000000..021909c60b --- /dev/null +++ b/accessible/base/AccEvent.cpp @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccEvent.h" + +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "xpcAccEvents.h" +#include "States.h" +#include "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, Accessible* 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 + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release) + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// AccTextChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +// Note: we pass in eAllowDupes to the base class because we don't support text +// events coalescence. We fire delayed text change events in DocAccessible but +// we continue to base the event off the accessible object rather than just the +// node. This means we won't try to create an accessible based on the node when +// we are ready to fire the event and so we will no longer assert at that point +// if the node was removed from the document. Either way, the AT won't work with +// a defunct accessible so the behaviour should be equivalent. +AccTextChangeEvent::AccTextChangeEvent(Accessible* aAccessible, int32_t aStart, + const nsAString& aModifiedText, + bool aIsInserted, + EIsFromUserInput aIsFromUserInput) + : AccEvent( + aIsInserted + ? static_cast(nsIAccessibleEvent::EVENT_TEXT_INSERTED) + : static_cast(nsIAccessibleEvent::EVENT_TEXT_REMOVED), + aAccessible, aIsFromUserInput, eAllowDupes), + mStart(aStart), + mIsInserted(aIsInserted), + mModifiedText(aModifiedText) { + // XXX We should use IsFromUserInput here, but that isn't always correct + // when the text change isn't related to content insertion or removal. + mIsFromUserInput = + mAccessible->State() & (states::FOCUSED | states::EDITABLE); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccHideEvent +//////////////////////////////////////////////////////////////////////////////// + +AccHideEvent::AccHideEvent(Accessible* aTarget, bool aNeedsShutdown) + : AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget), + mNeedsShutdown(aNeedsShutdown) { + mNextSibling = mAccessible->NextSibling(); + mPrevSibling = mAccessible->PrevSibling(); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccShowEvent +//////////////////////////////////////////////////////////////////////////////// + +AccShowEvent::AccShowEvent(Accessible* aTarget) + : AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget) { + int32_t idx = aTarget->IndexInParent(); + MOZ_ASSERT(idx >= 0); + mInsertionIndex = idx; +} + +//////////////////////////////////////////////////////////////////////////////// +// AccTextSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, + int32_t aReason) + : AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget, + eAutoDetect, eCoalesceTextSelChange), + mSel(aSelection), + mReason(aReason) {} + +AccTextSelChangeEvent::~AccTextSelChangeEvent() {} + +bool AccTextSelChangeEvent::IsCaretMoveOnly() const { + return mSel->RangeCount() == 1 && mSel->IsCollapsed() && + ((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON | + nsISelectionListener::COLLAPSETOEND_REASON)) == 0); +} + +void AccTextSelChangeEvent::SelectionRanges( + nsTArray* aRanges) const { + TextRange::TextRangesFromSelection(mSel, aRanges); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccSelChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccSelChangeEvent::AccSelChangeEvent(Accessible* aWidget, Accessible* aItem, + SelChangeType aSelChangeType) + : AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange), + mWidget(aWidget), + mItem(aItem), + mSelChangeType(aSelChangeType), + mPreceedingCount(0), + mPackedEvent(nullptr) { + if (aSelChangeType == eSelectionAdd) { + if (mWidget->GetSelectedItem(1)) + mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD; + else + mEventType = nsIAccessibleEvent::EVENT_SELECTION; + } else { + mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// AccTableChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccTableChangeEvent::AccTableChangeEvent(Accessible* aAccessible, + uint32_t aEventType, + int32_t aRowOrColIndex, + int32_t aNumRowsOrCols) + : AccEvent(aEventType, aAccessible), + mRowOrColIndex(aRowOrColIndex), + mNumRowsOrCols(aNumRowsOrCols) {} + +//////////////////////////////////////////////////////////////////////////////// +// AccVCChangeEvent +//////////////////////////////////////////////////////////////////////////////// + +AccVCChangeEvent::AccVCChangeEvent(Accessible* aAccessible, + Accessible* aOldAccessible, + int32_t aOldStart, int32_t aOldEnd, + Accessible* aNewAccessible, + int32_t aNewStart, int32_t aNewEnd, + int16_t aReason, int16_t aBoundaryType, + EIsFromUserInput aIsFromUserInput) + : AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible, + aIsFromUserInput), + mOldAccessible(aOldAccessible), + mNewAccessible(aNewAccessible), + mOldStart(aOldStart), + mNewStart(aNewStart), + mOldEnd(aOldEnd), + mNewEnd(aNewEnd), + mReason(aReason), + mBoundaryType(aBoundaryType) {} + +already_AddRefed a11y::MakeXPCEvent(AccEvent* aEvent) { + DocAccessible* doc = aEvent->Document(); + Accessible* 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()); + 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(std::move(ranges[idx]))); + } + + xpEvent = new xpcAccTextSelectionChangeEvent( + type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, xpcRanges); + return xpEvent.forget(); + } + + if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) { + AccVCChangeEvent* vcc = downcast_accEvent(aEvent); + xpEvent = new xpcAccVirtualCursorChangeEvent( + type, ToXPC(acc), ToXPCDocument(doc), node, fromUser, + ToXPC(vcc->OldAccessible()), vcc->OldStartOffset(), vcc->OldEndOffset(), + ToXPC(vcc->NewAccessible()), vcc->NewStartOffset(), vcc->NewEndOffset(), + vcc->Reason(), vcc->BoundaryType()); + 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..ade3e01b27 --- /dev/null +++ b/accessible/base/AccEvent.h @@ -0,0 +1,617 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _AccEvent_H_ +#define _AccEvent_H_ + +#include "nsIAccessibleEvent.h" + +#include "mozilla/a11y/Accessible.h" + +class nsEventShell; +namespace mozilla { + +namespace dom { +class Selection; +} + +namespace a11y { + +class DocAccessible; +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, Accessible* aAccessible, + EIsFromUserInput aIsFromUserInput = eAutoDetect, + EEventRule aEventRule = eRemoveDupes); + + // AccEvent + uint32_t GetEventType() const { return mEventType; } + EEventRule GetEventRule() const { return mEventRule; } + bool IsFromUserInput() const { return mIsFromUserInput; } + EIsFromUserInput FromUserInput() const { + return static_cast(mIsFromUserInput); + } + + Accessible* GetAccessible() const { return mAccessible; } + DocAccessible* Document() const { return mAccessible->Document(); } + + /** + * Down casting. + */ + enum EventGroup { + eGenericEvent, + eStateChangeEvent, + eTextChangeEvent, + eTreeMutationEvent, + eMutationEvent, + eReorderEvent, + eHideEvent, + eShowEvent, + eCaretMoveEvent, + eTextSelChangeEvent, + eSelectionChangeEvent, + eTableChangeEvent, + eVirtualCursorChangeEvent, + eObjectAttrChangedEvent, + 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(Accessible* aAccessible, uint64_t aState, bool aIsEnabled, + EIsFromUserInput aIsFromUserInput = eAutoDetect) + : AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, + aIsFromUserInput, eCoalesceStateChange), + mState(aState), + mIsEnabled(aIsEnabled) {} + + AccStateChangeEvent(Accessible* aAccessible, uint64_t aState) + : AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, + eAutoDetect, eCoalesceStateChange), + mState(aState) { + mIsEnabled = (mAccessible->State() & mState) != 0; + } + + // AccEvent + static const EventGroup kEventGroup = eStateChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eStateChangeEvent); + } + + // AccStateChangeEvent + uint64_t GetState() const { return mState; } + bool IsStateEnabled() const { return mIsEnabled; } + + private: + uint64_t mState; + bool mIsEnabled; + + friend class EventQueue; +}; + +/** + * Accessible text change event. + */ +class AccTextChangeEvent : public AccEvent { + public: + AccTextChangeEvent(Accessible* aAccessible, int32_t aStart, + const nsAString& aModifiedText, bool aIsInserted, + EIsFromUserInput aIsFromUserInput = eAutoDetect); + + // AccEvent + static const EventGroup kEventGroup = eTextChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eTextChangeEvent); + } + + // AccTextChangeEvent + int32_t GetStartOffset() const { return mStart; } + uint32_t GetLength() const { return mModifiedText.Length(); } + bool IsTextInserted() const { return mIsInserted; } + void GetModifiedText(nsAString& aModifiedText) { + aModifiedText = mModifiedText; + } + const nsString& ModifiedText() const { return mModifiedText; } + + private: + int32_t mStart; + bool mIsInserted; + nsString mModifiedText; + + friend class EventTree; + friend class NotificationController; +}; + +/** + * A base class for events related to tree mutation, either an AccMutation + * event, or an AccReorderEvent. + */ +class AccTreeMutationEvent : public AccEvent { + public: + AccTreeMutationEvent(uint32_t aEventType, Accessible* aTarget) + : AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder), + mGeneration(0) {} + + // Event + static const EventGroup kEventGroup = eTreeMutationEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent); + } + + void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; } + void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; } + AccTreeMutationEvent* NextEvent() const { return mNextEvent; } + AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; } + + /** + * A sequence number to know when this event was fired. + */ + uint32_t EventGeneration() const { return mGeneration; } + void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; } + + private: + RefPtr mNextEvent; + RefPtr mPrevEvent; + uint32_t mGeneration; +}; + +/** + * Base class for show and hide accessible events. + */ +class AccMutationEvent : public AccTreeMutationEvent { + public: + AccMutationEvent(uint32_t aEventType, Accessible* aTarget) + : AccTreeMutationEvent(aEventType, aTarget) { + // Don't coalesce these since they are coalesced by reorder event. Coalesce + // contained text change events. + mParent = mAccessible->Parent(); + } + virtual ~AccMutationEvent() {} + + // Event + static const EventGroup kEventGroup = eMutationEvent; + virtual unsigned int GetEventGroups() const override { + return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent); + } + + // MutationEvent + bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; } + bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; } + + Accessible* Parent() const { return mParent; } + + protected: + nsCOMPtr mNode; + RefPtr mParent; + RefPtr mTextChangeEvent; + + friend class EventTree; + friend class NotificationController; +}; + +/** + * Accessible hide event. + */ +class AccHideEvent : public AccMutationEvent { + public: + explicit AccHideEvent(Accessible* aTarget, bool aNeedsShutdown = true); + + // Event + static const EventGroup kEventGroup = eHideEvent; + virtual unsigned int GetEventGroups() const override { + return AccMutationEvent::GetEventGroups() | (1U << eHideEvent); + } + + // AccHideEvent + Accessible* TargetParent() const { return mParent; } + Accessible* TargetNextSibling() const { return mNextSibling; } + Accessible* TargetPrevSibling() const { return mPrevSibling; } + bool NeedsShutdown() const { return mNeedsShutdown; } + + protected: + bool mNeedsShutdown; + RefPtr mNextSibling; + RefPtr mPrevSibling; + + friend class EventTree; + friend class NotificationController; +}; + +/** + * Accessible show event. + */ +class AccShowEvent : public AccMutationEvent { + public: + explicit AccShowEvent(Accessible* aTarget); + + // Event + static const EventGroup kEventGroup = eShowEvent; + virtual unsigned int GetEventGroups() const override { + return AccMutationEvent::GetEventGroups() | (1U << eShowEvent); + } + + uint32_t InsertionIndex() const { return mInsertionIndex; } + + private: + nsTArray> mPrecedingEvents; + uint32_t mInsertionIndex; + + friend class EventTree; +}; + +/** + * Class for reorder accessible event. Takes care about + */ +class AccReorderEvent : public AccTreeMutationEvent { + public: + explicit AccReorderEvent(Accessible* aTarget) + : AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) {} + virtual ~AccReorderEvent() {} + + // Event + static const EventGroup kEventGroup = eReorderEvent; + virtual unsigned int GetEventGroups() const override { + return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent); + } +}; + +/** + * Accessible caret move event. + */ +class AccCaretMoveEvent : public AccEvent { + public: + AccCaretMoveEvent(Accessible* aAccessible, int32_t aCaretOffset, + bool aIsSelectionCollapsed, + EIsFromUserInput aIsFromUserInput = eAutoDetect) + : AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible, + aIsFromUserInput), + mCaretOffset(aCaretOffset), + mIsSelectionCollapsed(aIsSelectionCollapsed) {} + 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; } + + private: + int32_t mCaretOffset; + + bool mIsSelectionCollapsed; +}; + +/** + * Accessible text selection change event. + */ +class AccTextSelChangeEvent : public AccEvent { + public: + AccTextSelChangeEvent(HyperTextAccessible* aTarget, + dom::Selection* aSelection, int32_t aReason); + virtual ~AccTextSelChangeEvent(); + + // AccEvent + static const EventGroup kEventGroup = eTextSelChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent); + } + + // AccTextSelChangeEvent + + /** + * Return true if the text selection change wasn't caused by pure caret move. + */ + bool IsCaretMoveOnly() const; + + /** + * Return selection ranges in document/control. + */ + void SelectionRanges(nsTArray* aRanges) const; + + private: + RefPtr mSel; + int32_t mReason; + + friend class EventQueue; + friend class SelectionManager; +}; + +/** + * Accessible widget selection change event. + */ +class AccSelChangeEvent : public AccEvent { + public: + enum SelChangeType { eSelectionAdd, eSelectionRemove }; + + AccSelChangeEvent(Accessible* aWidget, Accessible* aItem, + SelChangeType aSelChangeType); + + virtual ~AccSelChangeEvent() {} + + // AccEvent + static const EventGroup kEventGroup = eSelectionChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent); + } + + // AccSelChangeEvent + Accessible* Widget() const { return mWidget; } + + private: + RefPtr mWidget; + RefPtr mItem; + SelChangeType mSelChangeType; + uint32_t mPreceedingCount; + AccSelChangeEvent* mPackedEvent; + + friend class EventQueue; +}; + +/** + * Accessible table change event. + */ +class AccTableChangeEvent : public AccEvent { + public: + AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType, + int32_t aRowOrColIndex, int32_t aNumRowsOrCols); + + // AccEvent + static const EventGroup kEventGroup = eTableChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eTableChangeEvent); + } + + // AccTableChangeEvent + uint32_t GetIndex() const { return mRowOrColIndex; } + uint32_t GetCount() const { return mNumRowsOrCols; } + + private: + uint32_t mRowOrColIndex; // the start row/column after which the rows are + // inserted/deleted. + uint32_t mNumRowsOrCols; // the number of inserted/deleted rows/columns +}; + +/** + * Accessible virtual cursor change event. + */ +class AccVCChangeEvent : public AccEvent { + public: + AccVCChangeEvent(Accessible* aAccessible, Accessible* aOldAccessible, + int32_t aOldStart, int32_t aOldEnd, + Accessible* aNewAccessible, int32_t aNewStart, + int32_t aNewEnd, int16_t aReason, int16_t aBoundaryType, + EIsFromUserInput aIsFromUserInput = eFromUserInput); + + virtual ~AccVCChangeEvent() {} + + // AccEvent + static const EventGroup kEventGroup = eVirtualCursorChangeEvent; + virtual unsigned int GetEventGroups() const override { + return AccEvent::GetEventGroups() | (1U << eVirtualCursorChangeEvent); + } + + // AccVCChangeEvent + Accessible* OldAccessible() const { return mOldAccessible; } + int32_t OldStartOffset() const { return mOldStart; } + int32_t OldEndOffset() const { return mOldEnd; } + Accessible* NewAccessible() const { return mNewAccessible; } + int32_t NewStartOffset() const { return mNewStart; } + int32_t NewEndOffset() const { return mNewEnd; } + int32_t Reason() const { return mReason; } + int32_t BoundaryType() const { return mBoundaryType; } + + private: + RefPtr mOldAccessible; + RefPtr mNewAccessible; + int32_t mOldStart; + int32_t mNewStart; + int32_t mOldEnd; + int32_t mNewEnd; + int16_t mReason; + int16_t mBoundaryType; +}; + +/** + * Accessible object attribute changed event. + */ +class AccObjectAttrChangedEvent : public AccEvent { + public: + AccObjectAttrChangedEvent(Accessible* 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, Accessible* 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(Accessible* 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..31153b87b5 --- /dev/null +++ b/accessible/base/AccGroupInfo.cpp @@ -0,0 +1,288 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccGroupInfo.h" +#include "nsAccUtils.h" +#include "TableAccessible.h" + +#include "Role.h" +#include "States.h" + +using namespace mozilla::a11y; + +AccGroupInfo::AccGroupInfo(const Accessible* aItem, role aRole) + : mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole) { + MOZ_COUNT_CTOR(AccGroupInfo); + Update(); +} + +void AccGroupInfo::Update() { + mParent = nullptr; + + Accessible* parent = mItem->Parent(); + if (!parent) return; + + int32_t indexInParent = mItem->IndexInParent(); + uint32_t siblingCount = parent->ChildCount(); + if (indexInParent == -1 || + indexInParent >= static_cast(siblingCount)) { + NS_ERROR("Wrong index in parent! Tree invalidation problem."); + return; + } + + int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem); + + // Compute position in set. + mPosInSet = 1; + for (int32_t idx = indexInParent - 1; idx >= 0; idx--) { + Accessible* sibling = parent->GetChildAt(idx); + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) break; + + if (BaseRole(siblingRole) != mRole) { + continue; + } + bool siblingHasGroupInfo = + sibling->mBits.groupInfo && !sibling->HasDirtyGroupInfo(); + // Skip invisible siblings. + // If the sibling has calculated group info, that means it's visible. + if (!siblingHasGroupInfo && sibling->State() & states::INVISIBLE) { + continue; + } + + // Check if it's hierarchical flatten structure, i.e. if the sibling + // level is lesser than this one then group is ended, if the sibling level + // is greater than this one then the group is split by some child elements + // (group will be continued). + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) { + mParent = sibling; + break; + } + + // Skip subset. + if (siblingLevel > level) continue; + + // If the previous item in the group has calculated group information then + // build group information for this item based on found one. + if (siblingHasGroupInfo) { + mPosInSet += sibling->mBits.groupInfo->mPosInSet; + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mPosInSet++; + } + + // Compute set size. + mSetSize = mPosInSet; + + for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) { + Accessible* sibling = parent->GetChildAt(idx); + + roles::Role siblingRole = sibling->Role(); + + // If the sibling is separator then the group is ended. + if (siblingRole == roles::SEPARATOR) break; + + if (BaseRole(siblingRole) != mRole) { + continue; + } + bool siblingHasGroupInfo = + sibling->mBits.groupInfo && !sibling->HasDirtyGroupInfo(); + // Skip invisible siblings. + // If the sibling has calculated group info, that means it's visible. + if (!siblingHasGroupInfo && sibling->State() & states::INVISIBLE) { + continue; + } + + // and check if it's hierarchical flatten structure. + int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling); + if (siblingLevel < level) break; + + // Skip subset. + if (siblingLevel > level) continue; + + // If the next item in the group has calculated group information then + // build group information for this item based on found one. + if (siblingHasGroupInfo) { + mParent = sibling->mBits.groupInfo->mParent; + mSetSize = sibling->mBits.groupInfo->mSetSize; + return; + } + + mSetSize++; + } + + if (mParent) return; + + roles::Role parentRole = parent->Role(); + if (ShouldReportRelations(mRole, parentRole)) mParent = parent; + + // ARIA tree and list can be arranged by using ARIA groups to organize levels. + if (parentRole != roles::GROUPING) return; + + // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a + // parent. In other words the parent of the tree item will be a group and + // the previous tree item of the group is a conceptual parent of the tree + // item. + if (mRole == roles::OUTLINEITEM) { + Accessible* parentPrevSibling = parent->PrevSibling(); + if (parentPrevSibling && parentPrevSibling->Role() == mRole) { + mParent = parentPrevSibling; + return; + } + } + + // Way #2 for ARIA list and tree: group is a child of an item. In other words + // the parent of the item will be a group and containing item of the group is + // a conceptual parent of the item. + if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) { + Accessible* grandParent = parent->Parent(); + if (grandParent && grandParent->Role() == mRole) mParent = grandParent; + } +} + +Accessible* AccGroupInfo::FirstItemOf(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->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // ARIA list and tree can be arranged by ARIA groups case #2 (group is + // a child of an item). + item = aContainer->LastChild(); + if (!item) return nullptr; + + if (item->Role() == roles::GROUPING && + (containerRole == roles::LISTITEM || + containerRole == roles::OUTLINEITEM)) { + item = item->FirstChild(); + if (item) { + AccGroupInfo* itemGroupInfo = item->GetGroupInfo(); + if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer) + return item; + } + } + + // Otherwise, it can be a direct child if the container is a list or tree. + item = aContainer->FirstChild(); + if (ShouldReportRelations(item->Role(), containerRole)) return item; + + return nullptr; +} + +uint32_t AccGroupInfo::TotalItemCount(Accessible* aContainer, + bool* aIsHierarchical) { + uint32_t itemCount = 0; + switch (aContainer->Role()) { + case roles::TABLE: + if (nsCoreUtils::GetUIntAttr(aContainer->GetContent(), + nsGkAtoms::aria_rowcount, + (int32_t*)&itemCount)) { + break; + } + + if (TableAccessible* tableAcc = aContainer->AsTable()) { + return tableAcc->RowCount(); + } + + break; + case roles::ROW: + if (Accessible* table = nsAccUtils::TableFor(aContainer)) { + if (nsCoreUtils::GetUIntAttr(table->GetContent(), + nsGkAtoms::aria_colcount, + (int32_t*)&itemCount)) { + break; + } + + 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->GetGroupInfo(); + if (!groupInfo) return nullptr; + + // If the item in middle of the group then search next item in siblings. + if (groupInfo->PosInSet() >= groupInfo->SetSize()) return nullptr; + + Accessible* parent = aItem->Parent(); + uint32_t childCount = parent->ChildCount(); + for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) { + Accessible* nextItem = parent->GetChildAt(idx); + AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo(); + if (nextGroupInfo && + nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) { + return nextItem; + } + } + + MOZ_ASSERT_UNREACHABLE( + "Item in the middle of the group but there's no next item!"); + return nullptr; +} + +bool AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole) { + // We only want to report hierarchy-based node relations for items in tree or + // list form. ARIA level/owns relations are always reported. + if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM) return true; + if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW) return true; + if (aParentRole == roles::LIST && aRole == roles::LISTITEM) return true; + + return false; +} diff --git a/accessible/base/AccGroupInfo.h b/accessible/base/AccGroupInfo.h new file mode 100644 index 0000000000..070e258ea3 --- /dev/null +++ b/accessible/base/AccGroupInfo.h @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AccGroupInfo_h_ +#define AccGroupInfo_h_ + +#include "Accessible-inl.h" + +namespace mozilla { +namespace a11y { + +/** + * Calculate and store group information. + */ +class AccGroupInfo { + public: + MOZ_COUNTED_DTOR(AccGroupInfo) + + /** + * Return 1-based position in the group. + */ + uint32_t PosInSet() const { return mPosInSet; } + + /** + * Return a number of items in the group. + */ + uint32_t SetSize() const { return mSetSize; } + + /** + * Return a direct or logical parent of the accessible that this group info is + * created for. + */ + Accessible* ConceptualParent() const { return mParent; } + + /** + * Update group information. + */ + void Update(); + + /** + * Create group info. + */ + static AccGroupInfo* CreateGroupInfo(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; + } + + /** + * 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); + + protected: + AccGroupInfo(const Accessible* aItem, a11y::role aRole); + + private: + AccGroupInfo() = delete; + AccGroupInfo(const AccGroupInfo&) = delete; + AccGroupInfo& operator=(const AccGroupInfo&) = delete; + + static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole) { + if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM || + aRole == mozilla::a11y::roles::PARENT_MENUITEM || + aRole == mozilla::a11y::roles::RADIO_MENU_ITEM) + return mozilla::a11y::roles::MENUITEM; + + if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION) + return mozilla::a11y::roles::RICH_OPTION; + + return aRole; + } + + /** + * Return true if the given parent and child roles should have their node + * relations reported. + */ + static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole); + + uint32_t mPosInSet; + uint32_t mSetSize; + Accessible* mParent; + 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..2314843f22 --- /dev/null +++ b/accessible/base/AccIterator.cpp @@ -0,0 +1,338 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccIterator.h" + +#include "AccGroupInfo.h" +#ifdef MOZ_XUL +# include "XULTreeAccessible.h" +#endif + +#include "mozilla/dom/DocumentOrShadowRoot.h" +#include "mozilla/dom/HTMLLabelElement.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// AccIterator +//////////////////////////////////////////////////////////////////////////////// + +AccIterator::AccIterator(const Accessible* aAccessible, + filters::FilterFuncPtr aFilterFunc) + : mFilterFunc(aFilterFunc) { + mState = new IteratorState(aAccessible); +} + +AccIterator::~AccIterator() { + while (mState) { + IteratorState* tmp = mState; + mState = tmp->mParentState; + delete tmp; + } +} + +Accessible* AccIterator::Next() { + while (mState) { + Accessible* child = mState->mParent->GetChildAt(mState->mIndex++); + if (!child) { + IteratorState* tmp = mState; + mState = mState->mParentState; + delete tmp; + + continue; + } + + uint32_t result = mFilterFunc(child); + if (result & filters::eMatch) return child; + + if (!(result & filters::eSkipSubtree)) { + IteratorState* childState = new IteratorState(child, mState); + mState = childState; + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAccIterator::IteratorState + +AccIterator::IteratorState::IteratorState(const Accessible* 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(kNameSpaceID_None, nsGkAtoms::id, + id)) { + mProviders = mDocument->GetRelProviders(aDependentContent->AsElement(), id); + } +} + +Accessible* 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) { + Accessible* related = mDocument->GetAccessible(provider->mContent); + if (related) { + return related; + } + + // If the document content is pointed by relation then return the + // document itself. + if (provider->mContent == mDocument->GetContent()) { + return mDocument; + } + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLabelIterator +//////////////////////////////////////////////////////////////////////////////// + +HTMLLabelIterator::HTMLLabelIterator(DocAccessible* aDocument, + const Accessible* aAccessible, + LabelFilter aFilter) + : mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for), + mAcc(aAccessible), + mLabelFilter(aFilter) {} + +bool HTMLLabelIterator::IsLabel(Accessible* aLabel) { + dom::HTMLLabelElement* labelEl = + dom::HTMLLabelElement::FromNode(aLabel->GetContent()); + return labelEl && labelEl->GetControl() == mAcc->GetContent(); +} + +Accessible* HTMLLabelIterator::Next() { + // Get either