summaryrefslogtreecommitdiffstats
path: root/accessible/base
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /accessible/base
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--accessible/base/ARIAMap.cpp1516
-rw-r--r--accessible/base/ARIAMap.h305
-rw-r--r--accessible/base/ARIAStateMap.cpp330
-rw-r--r--accessible/base/ARIAStateMap.h66
-rw-r--r--accessible/base/AccEvent.cpp305
-rw-r--r--accessible/base/AccEvent.h617
-rw-r--r--accessible/base/AccGroupInfo.cpp288
-rw-r--r--accessible/base/AccGroupInfo.h118
-rw-r--r--accessible/base/AccIterator.cpp338
-rw-r--r--accessible/base/AccIterator.h303
-rw-r--r--accessible/base/AccTypes.h97
-rw-r--r--accessible/base/AccessibleOrProxy.cpp97
-rw-r--r--accessible/base/AccessibleOrProxy.h180
-rw-r--r--accessible/base/Asserts.cpp29
-rw-r--r--accessible/base/DocManager.cpp562
-rw-r--r--accessible/base/DocManager.h189
-rw-r--r--accessible/base/EmbeddedObjCollector.cpp63
-rw-r--r--accessible/base/EmbeddedObjCollector.h68
-rw-r--r--accessible/base/EventQueue.cpp335
-rw-r--r--accessible/base/EventQueue.h76
-rw-r--r--accessible/base/EventTree.cpp601
-rw-r--r--accessible/base/EventTree.h128
-rw-r--r--accessible/base/Filters.cpp44
-rw-r--r--accessible/base/Filters.h46
-rw-r--r--accessible/base/FocusManager.cpp414
-rw-r--r--accessible/base/FocusManager.h146
-rw-r--r--accessible/base/IDSet.h129
-rw-r--r--accessible/base/Logging.cpp925
-rw-r--r--accessible/base/Logging.h230
-rw-r--r--accessible/base/MarkupMap.h561
-rw-r--r--accessible/base/NotificationController.cpp974
-rw-r--r--accessible/base/NotificationController.h462
-rw-r--r--accessible/base/Pivot.cpp596
-rw-r--r--accessible/base/Pivot.h108
-rw-r--r--accessible/base/Platform.h160
-rw-r--r--accessible/base/Relation.h98
-rw-r--r--accessible/base/RelationType.h163
-rw-r--r--accessible/base/RelationTypeMap.h87
-rw-r--r--accessible/base/Role.h1090
-rw-r--r--accessible/base/RoleMap.h1879
-rw-r--r--accessible/base/SelectionManager.cpp207
-rw-r--r--accessible/base/SelectionManager.h130
-rw-r--r--accessible/base/States.h297
-rw-r--r--accessible/base/Statistics.h42
-rw-r--r--accessible/base/StyleInfo.cpp107
-rw-r--r--accessible/base/StyleInfo.h50
-rw-r--r--accessible/base/TextAttrs.cpp750
-rw-r--r--accessible/base/TextAttrs.h433
-rw-r--r--accessible/base/TextRange-inl.h26
-rw-r--r--accessible/base/TextRange.cpp518
-rw-r--r--accessible/base/TextRange.h283
-rw-r--r--accessible/base/TextUpdater.cpp189
-rw-r--r--accessible/base/TextUpdater.h95
-rw-r--r--accessible/base/TreeWalker.cpp348
-rw-r--r--accessible/base/TreeWalker.h142
-rw-r--r--accessible/base/XULMap.h126
-rw-r--r--accessible/base/moz.build117
-rw-r--r--accessible/base/nsAccCache.h24
-rw-r--r--accessible/base/nsAccUtils.cpp519
-rw-r--r--accessible/base/nsAccUtils.h270
-rw-r--r--accessible/base/nsAccessibilityService.cpp1768
-rw-r--r--accessible/base/nsAccessibilityService.h507
-rw-r--r--accessible/base/nsAccessiblePivot.cpp522
-rw-r--r--accessible/base/nsAccessiblePivot.h138
-rw-r--r--accessible/base/nsCoreUtils.cpp587
-rw-r--r--accessible/base/nsCoreUtils.h328
-rw-r--r--accessible/base/nsEventShell.cpp71
-rw-r--r--accessible/base/nsEventShell.h66
-rw-r--r--accessible/base/nsTextEquivUtils.cpp355
-rw-r--r--accessible/base/nsTextEquivUtils.h164
70 files changed, 23902 insertions, 0 deletions
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 <stdint.h>
+
+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<bool>(eNoUserInput) == false &&
+ static_cast<bool>(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<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_INSERTED)
+ : static_cast<uint32_t>(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<TextRange>* 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<nsIAccessibleEvent> 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<nsIAccessibleEvent> 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<TextRange, 1> ranges;
+ tsc->SelectionRanges(&ranges);
+
+ nsCOMPtr<nsIMutableArray> 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<EIsFromUserInput>(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<Accessible> 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<AccTreeMutationEvent> mNextEvent;
+ RefPtr<AccTreeMutationEvent> 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<nsINode> mNode;
+ RefPtr<Accessible> mParent;
+ RefPtr<AccTextChangeEvent> 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<Accessible> mNextSibling;
+ RefPtr<Accessible> 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<RefPtr<AccHideEvent>> 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<a11y::TextRange>* aRanges) const;
+
+ private:
+ RefPtr<dom::Selection> 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<Accessible> mWidget;
+ RefPtr<Accessible> 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<Accessible> mOldAccessible;
+ RefPtr<Accessible> 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<nsAtom> 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 <class Destination>
+ operator Destination*() {
+ if (!mRawPtr) return nullptr;
+
+ return mRawPtr->GetEventGroups() & (1U << Destination::kEventGroup)
+ ? static_cast<Destination*>(mRawPtr)
+ : nullptr;
+ }
+
+ private:
+ AccEvent* mRawPtr;
+};
+
+/**
+ * Return a new xpcom accessible event for the given internal one.
+ */
+already_AddRefed<nsIAccessibleEvent> 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<int32_t>(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 <label for="[id]"> element which explicitly points to given
+ // element, or <label> ancestor which implicitly point to it.
+ Accessible* label = nullptr;
+ while ((label = mRelIter.Next())) {
+ if (IsLabel(label)) {
+ return label;
+ }
+ }
+
+ // Ignore ancestor label on not widget accessible.
+ if (mLabelFilter == eSkipAncestorLabel || !mAcc->IsWidget()) return nullptr;
+
+ // Go up tree to get a name of ancestor label if there is one (an ancestor
+ // <label> implicitly points to us). Don't go up farther than form or
+ // document.
+ Accessible* walkUp = mAcc->Parent();
+ while (walkUp && !walkUp->IsDoc()) {
+ nsIContent* walkUpEl = walkUp->GetContent();
+ if (IsLabel(walkUp) &&
+ !walkUpEl->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::_for)) {
+ mLabelFilter = eSkipAncestorLabel; // prevent infinite loop
+ return walkUp;
+ }
+
+ if (walkUpEl->IsHTMLElement(nsGkAtoms::form)) break;
+
+ walkUp = walkUp->Parent();
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLOutputIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLOutputIterator::HTMLOutputIterator(DocAccessible* aDocument,
+ nsIContent* aElement)
+ : mRelIter(aDocument, aElement, nsGkAtoms::_for) {}
+
+Accessible* HTMLOutputIterator::Next() {
+ Accessible* output = nullptr;
+ while ((output = mRelIter.Next())) {
+ if (output->GetContent()->IsHTMLElement(nsGkAtoms::output)) return output;
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULLabelIterator::XULLabelIterator(DocAccessible* aDocument,
+ nsIContent* aElement)
+ : mRelIter(aDocument, aElement, nsGkAtoms::control) {}
+
+Accessible* XULLabelIterator::Next() {
+ Accessible* label = nullptr;
+ while ((label = mRelIter.Next())) {
+ if (label->GetContent()->IsXULElement(nsGkAtoms::label)) return label;
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULDescriptionIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULDescriptionIterator::XULDescriptionIterator(DocAccessible* aDocument,
+ nsIContent* aElement)
+ : mRelIter(aDocument, aElement, nsGkAtoms::control) {}
+
+Accessible* XULDescriptionIterator::Next() {
+ Accessible* descr = nullptr;
+ while ((descr = mRelIter.Next())) {
+ if (descr->GetContent()->IsXULElement(nsGkAtoms::description)) return descr;
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IDRefsIterator
+////////////////////////////////////////////////////////////////////////////////
+
+IDRefsIterator::IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
+ nsAtom* aIDRefsAttr)
+ : mContent(aContent), mDoc(aDoc), mCurrIdx(0) {
+ if (mContent->IsElement()) {
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs);
+ }
+}
+
+const nsDependentSubstring IDRefsIterator::NextID() {
+ for (; mCurrIdx < mIDs.Length(); mCurrIdx++) {
+ if (!NS_IsAsciiWhitespace(mIDs[mCurrIdx])) break;
+ }
+
+ if (mCurrIdx >= mIDs.Length()) return nsDependentSubstring();
+
+ nsAString::index_type idStartIdx = mCurrIdx;
+ while (++mCurrIdx < mIDs.Length()) {
+ if (NS_IsAsciiWhitespace(mIDs[mCurrIdx])) break;
+ }
+
+ return Substring(mIDs, idStartIdx, mCurrIdx++ - idStartIdx);
+}
+
+nsIContent* IDRefsIterator::NextElem() {
+ while (true) {
+ const nsDependentSubstring id = NextID();
+ if (id.IsEmpty()) break;
+
+ nsIContent* refContent = GetElem(id);
+ if (refContent) return refContent;
+ }
+
+ return nullptr;
+}
+
+dom::Element* IDRefsIterator::GetElem(nsIContent* aContent,
+ const nsAString& aID) {
+ // Get elements in DOM tree by ID attribute if this is an explicit content.
+ // In case of bound element check its anonymous subtree.
+ if (!aContent->IsInNativeAnonymousSubtree()) {
+ dom::DocumentOrShadowRoot* docOrShadowRoot =
+ aContent->GetUncomposedDocOrConnectedShadowRoot();
+ if (docOrShadowRoot) {
+ dom::Element* refElm = docOrShadowRoot->GetElementById(aID);
+ if (refElm) {
+ return refElm;
+ }
+ }
+ }
+ return nullptr;
+}
+
+dom::Element* IDRefsIterator::GetElem(const nsDependentSubstring& aID) {
+ return GetElem(mContent, aID);
+}
+
+Accessible* IDRefsIterator::Next() {
+ nsIContent* nextEl = nullptr;
+ while ((nextEl = NextElem())) {
+ Accessible* acc = mDoc->GetAccessible(nextEl);
+ if (acc) {
+ return acc;
+ }
+ }
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SingleAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+Accessible* SingleAccIterator::Next() {
+ RefPtr<Accessible> nextAcc;
+ mAcc.swap(nextAcc);
+ if (!nextAcc || nextAcc->IsDefunct()) {
+ return nullptr;
+ }
+ return nextAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+Accessible* ItemIterator::Next() {
+ if (mContainer) {
+ mAnchor = AccGroupInfo::FirstItemOf(mContainer);
+ mContainer = nullptr;
+ return mAnchor;
+ }
+
+ return mAnchor ? (mAnchor = AccGroupInfo::NextItemTo(mAnchor)) : nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeItemIterator::XULTreeItemIterator(const XULTreeAccessible* aXULTree,
+ nsITreeView* aTreeView,
+ int32_t aRowIdx)
+ : mXULTree(aXULTree),
+ mTreeView(aTreeView),
+ mRowCount(-1),
+ mContainerLevel(-1),
+ mCurrRowIdx(aRowIdx + 1) {
+ mTreeView->GetRowCount(&mRowCount);
+ if (aRowIdx != -1) mTreeView->GetLevel(aRowIdx, &mContainerLevel);
+}
+
+Accessible* XULTreeItemIterator::Next() {
+ while (mCurrRowIdx < mRowCount) {
+ int32_t level = 0;
+ mTreeView->GetLevel(mCurrRowIdx, &level);
+
+ if (level == mContainerLevel + 1)
+ return mXULTree->GetTreeItemAccessible(mCurrRowIdx++);
+
+ if (level <= mContainerLevel) { // got level up
+ mCurrRowIdx = mRowCount;
+ break;
+ }
+
+ mCurrRowIdx++;
+ }
+
+ return nullptr;
+}
diff --git a/accessible/base/AccIterator.h b/accessible/base/AccIterator.h
new file mode 100644
index 0000000000..7cd48c870f
--- /dev/null
+++ b/accessible/base/AccIterator.h
@@ -0,0 +1,303 @@
+/* -*- 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_AccIterator_h__
+#define mozilla_a11y_AccIterator_h__
+
+#include "DocAccessible.h"
+#include "Filters.h"
+
+#include <memory>
+
+class nsITreeView;
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * AccIterable is a basic interface for iterators over accessibles.
+ */
+class AccIterable {
+ public:
+ virtual ~AccIterable() {}
+ virtual Accessible* Next() = 0;
+
+ private:
+ friend class Relation;
+ std::unique_ptr<AccIterable> mNextIter;
+};
+
+/**
+ * Allows to iterate through accessible children or subtree complying with
+ * filter function.
+ */
+class AccIterator : public AccIterable {
+ public:
+ AccIterator(const Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
+ virtual ~AccIterator();
+
+ /**
+ * Return next accessible complying with filter function. Return the first
+ * accessible for the first time.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ AccIterator();
+ AccIterator(const AccIterator&);
+ AccIterator& operator=(const AccIterator&);
+
+ struct IteratorState {
+ explicit IteratorState(const Accessible* aParent,
+ IteratorState* mParentState = nullptr);
+
+ const Accessible* mParent;
+ int32_t mIndex;
+ IteratorState* mParentState;
+ };
+
+ filters::FilterFuncPtr mFilterFunc;
+ IteratorState* mState;
+};
+
+/**
+ * Allows to traverse through related accessibles that are pointing to the given
+ * dependent accessible by relation attribute.
+ */
+class RelatedAccIterator : public AccIterable {
+ public:
+ /**
+ * Constructor.
+ *
+ * @param aDocument [in] the document accessible the related
+ * & accessibles belong to.
+ * @param aDependentContent [in] the content of dependent accessible that
+ * relations were requested for
+ * @param aRelAttr [in] relation attribute that relations are
+ * pointed by
+ */
+ RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
+ nsAtom* aRelAttr);
+
+ virtual ~RelatedAccIterator() {}
+
+ /**
+ * Return next related accessible for the given dependent accessible.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ RelatedAccIterator();
+ RelatedAccIterator(const RelatedAccIterator&);
+ RelatedAccIterator& operator=(const RelatedAccIterator&);
+
+ DocAccessible* mDocument;
+ nsAtom* mRelAttr;
+ DocAccessible::AttrRelProviders* mProviders;
+ uint32_t mIndex;
+};
+
+/**
+ * Used to iterate through HTML labels associated with the given accessible.
+ */
+class HTMLLabelIterator : public AccIterable {
+ public:
+ enum LabelFilter { eAllLabels, eSkipAncestorLabel };
+
+ HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
+ LabelFilter aFilter = eAllLabels);
+
+ virtual ~HTMLLabelIterator() {}
+
+ /**
+ * Return next label accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ HTMLLabelIterator();
+ HTMLLabelIterator(const HTMLLabelIterator&);
+ HTMLLabelIterator& operator=(const HTMLLabelIterator&);
+
+ bool IsLabel(Accessible* aLabel);
+
+ RelatedAccIterator mRelIter;
+ // XXX: replace it on weak reference (bug 678429), it's safe to use raw
+ // pointer now because iterators life cycle is short.
+ const Accessible* mAcc;
+ LabelFilter mLabelFilter;
+};
+
+/**
+ * Used to iterate through HTML outputs associated with the given element.
+ */
+class HTMLOutputIterator : public AccIterable {
+ public:
+ HTMLOutputIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~HTMLOutputIterator() {}
+
+ /**
+ * Return next output accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ HTMLOutputIterator();
+ HTMLOutputIterator(const HTMLOutputIterator&);
+ HTMLOutputIterator& operator=(const HTMLOutputIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+/**
+ * Used to iterate through XUL labels associated with the given element.
+ */
+class XULLabelIterator : public AccIterable {
+ public:
+ XULLabelIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~XULLabelIterator() {}
+
+ /**
+ * Return next label accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ XULLabelIterator();
+ XULLabelIterator(const XULLabelIterator&);
+ XULLabelIterator& operator=(const XULLabelIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+/**
+ * Used to iterate through XUL descriptions associated with the given element.
+ */
+class XULDescriptionIterator : public AccIterable {
+ public:
+ XULDescriptionIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~XULDescriptionIterator() {}
+
+ /**
+ * Return next description accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+ private:
+ XULDescriptionIterator();
+ XULDescriptionIterator(const XULDescriptionIterator&);
+ XULDescriptionIterator& operator=(const XULDescriptionIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+/**
+ * Used to iterate through IDs, elements or accessibles pointed by IDRefs
+ * attribute. Note, any method used to iterate through IDs, elements, or
+ * accessibles moves iterator to next position.
+ */
+class IDRefsIterator : public AccIterable {
+ public:
+ IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
+ nsAtom* aIDRefsAttr);
+ virtual ~IDRefsIterator() {}
+
+ /**
+ * Return next ID.
+ */
+ const nsDependentSubstring NextID();
+
+ /**
+ * Return next element.
+ */
+ nsIContent* NextElem();
+
+ /**
+ * Return the element with the given ID.
+ */
+ static dom::Element* GetElem(nsIContent* aContent, const nsAString& aID);
+ dom::Element* GetElem(const nsDependentSubstring& aID);
+
+ // AccIterable
+ virtual Accessible* Next() override;
+
+ private:
+ IDRefsIterator();
+ IDRefsIterator(const IDRefsIterator&);
+ IDRefsIterator operator=(const IDRefsIterator&);
+
+ nsString mIDs;
+ nsIContent* mContent;
+ DocAccessible* mDoc;
+ nsAString::index_type mCurrIdx;
+};
+
+/**
+ * Iterator that points to a single accessible returning it on the first call
+ * to Next().
+ */
+class SingleAccIterator : public AccIterable {
+ public:
+ explicit SingleAccIterator(Accessible* aTarget) : mAcc(aTarget) {}
+ virtual ~SingleAccIterator() {}
+
+ virtual Accessible* Next() override;
+
+ private:
+ SingleAccIterator();
+ SingleAccIterator(const SingleAccIterator&);
+ SingleAccIterator& operator=(const SingleAccIterator&);
+
+ RefPtr<Accessible> mAcc;
+};
+
+/**
+ * Used to iterate items of the given item container.
+ */
+class ItemIterator : public AccIterable {
+ public:
+ explicit ItemIterator(const Accessible* aItemContainer)
+ : mContainer(aItemContainer), mAnchor(nullptr) {}
+ virtual ~ItemIterator() {}
+
+ virtual Accessible* Next() override;
+
+ private:
+ ItemIterator() = delete;
+ ItemIterator(const ItemIterator&) = delete;
+ ItemIterator& operator=(const ItemIterator&) = delete;
+
+ const Accessible* mContainer;
+ Accessible* mAnchor;
+};
+
+/**
+ * Used to iterate through XUL tree items of the same level.
+ */
+class XULTreeItemIterator : public AccIterable {
+ public:
+ XULTreeItemIterator(const XULTreeAccessible* aXULTree, nsITreeView* aTreeView,
+ int32_t aRowIdx);
+ virtual ~XULTreeItemIterator() {}
+
+ virtual Accessible* Next() override;
+
+ private:
+ XULTreeItemIterator() = delete;
+ XULTreeItemIterator(const XULTreeItemIterator&) = delete;
+ XULTreeItemIterator& operator=(const XULTreeItemIterator&) = delete;
+
+ const XULTreeAccessible* mXULTree;
+ nsITreeView* mTreeView;
+ int32_t mRowCount;
+ int32_t mContainerLevel;
+ int32_t mCurrRowIdx;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/AccTypes.h b/accessible/base/AccTypes.h
new file mode 100644
index 0000000000..aa5dabab9c
--- /dev/null
+++ b/accessible/base/AccTypes.h
@@ -0,0 +1,97 @@
+/* -*- 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_AccTypes_h
+#define mozilla_a11y_AccTypes_h
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible object types. Each accessible class can have own type.
+ */
+enum AccType {
+ /**
+ * This set of types is used for accessible creation, keep them together in
+ * alphabetical order since they are used in switch statement.
+ */
+ eNoType,
+ eHTMLBRType,
+ eHTMLButtonType,
+ eHTMLCanvasType,
+ eHTMLCaptionType,
+ eHTMLCheckboxType,
+ eHTMLComboboxType,
+ eHTMLFileInputType,
+ eHTMLGroupboxType,
+ eHTMLHRType,
+ eHTMLImageMapType,
+ eHTMLLiType,
+ eHTMLSelectListType,
+ eHTMLMediaType,
+ eHTMLRadioButtonType,
+ eHTMLRangeType,
+ eHTMLSpinnerType,
+ eHTMLTableType,
+ eHTMLTableCellType,
+ eHTMLTableRowType,
+ eHTMLTextFieldType,
+ eHTMLTextPasswordFieldType,
+ eHyperTextType,
+ eImageType,
+ eOuterDocType,
+ ePluginType,
+ eTextLeafType,
+
+ /**
+ * Other accessible types.
+ */
+ eApplicationType,
+ eHTMLLinkType,
+ eHTMLOptGroupType,
+ eImageMapType,
+ eMenuPopupType,
+ eProxyType,
+ eProgressType,
+ eRootType,
+ eXULLabelType,
+ eXULListItemType,
+ eXULTabpanelsType,
+ eXULTooltipType,
+ eXULTreeType,
+
+ eLastAccType = eXULTreeType
+};
+
+/**
+ * Generic accessible type, different accessible classes can share the same
+ * type, the same accessible class can have several types.
+ */
+enum AccGenericType {
+ eAlert = 1 << 0,
+ eAutoComplete = 1 << 1,
+ eAutoCompletePopup = 1 << 2,
+ eButton = 1 << 3,
+ eCombobox = 1 << 4,
+ eDocument = 1 << 5,
+ eHyperText = 1 << 6,
+ eLandmark = 1 << 7,
+ eList = 1 << 8,
+ eListControl = 1 << 9,
+ eMenuButton = 1 << 10,
+ eSelect = 1 << 11,
+ eTable = 1 << 12,
+ eTableCell = 1 << 13,
+ eTableRow = 1 << 14,
+ eText = 1 << 15,
+
+ eLastAccGenericType = eText
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_AccTypes_h
diff --git a/accessible/base/AccessibleOrProxy.cpp b/accessible/base/AccessibleOrProxy.cpp
new file mode 100644
index 0000000000..3826e6968c
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 "AccessibleOrProxy.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/OuterDocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+int32_t AccessibleOrProxy::IndexInParent() const {
+ if (IsAccessible()) {
+ return AsAccessible()->IndexInParent();
+ }
+
+ ProxyAccessible* proxy = AsProxy();
+ if (!proxy) {
+ return -1;
+ }
+
+ if (proxy->Parent()) {
+ return proxy->IndexInParent();
+ }
+
+ if (proxy->OuterDocOfRemoteBrowser()) {
+ return 0;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Proxy should have parent or outer doc.");
+ return -1;
+}
+
+AccessibleOrProxy AccessibleOrProxy::Parent() const {
+ if (IsAccessible()) {
+ return AsAccessible()->Parent();
+ }
+
+ ProxyAccessible* proxy = AsProxy();
+ if (!proxy) {
+ return nullptr;
+ }
+
+ if (ProxyAccessible* parent = proxy->Parent()) {
+ return parent;
+ }
+
+ // Otherwise this should be the proxy for the tab's top level document.
+ return proxy->OuterDocOfRemoteBrowser();
+}
+
+AccessibleOrProxy AccessibleOrProxy::ChildAtPoint(
+ int32_t aX, int32_t aY, Accessible::EWhichChildAtPoint aWhichChild) {
+ if (IsProxy()) {
+ return AsProxy()->ChildAtPoint(aX, aY, aWhichChild);
+ }
+ ProxyAccessible* childDoc = RemoteChildDoc();
+ if (childDoc) {
+ // This is an OuterDocAccessible.
+ nsIntRect docRect = AsAccessible()->Bounds();
+ if (!docRect.Contains(aX, aY)) {
+ return nullptr;
+ }
+ if (aWhichChild == Accessible::eDirectChild) {
+ return childDoc;
+ }
+ return childDoc->ChildAtPoint(aX, aY, aWhichChild);
+ }
+ AccessibleOrProxy target = AsAccessible()->ChildAtPoint(aX, aY, aWhichChild);
+ if (target.IsNull() || aWhichChild == Accessible::eDirectChild) {
+ return target;
+ }
+ childDoc = target.RemoteChildDoc();
+ if (childDoc) {
+ // Accessible::ChildAtPoint stopped at an OuterDocAccessible, since it
+ // can't traverse into ProxyAccessibles. Continue the search from childDoc.
+ return childDoc->ChildAtPoint(aX, aY, aWhichChild);
+ }
+ return target;
+}
+
+ProxyAccessible* AccessibleOrProxy::RemoteChildDoc() const {
+ MOZ_ASSERT(!IsNull());
+ if (IsProxy()) {
+ return nullptr;
+ }
+ OuterDocAccessible* outerDoc = AsAccessible()->AsOuterDoc();
+ if (!outerDoc) {
+ return nullptr;
+ }
+ return outerDoc->RemoteChildDoc();
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/AccessibleOrProxy.h b/accessible/base/AccessibleOrProxy.h
new file mode 100644
index 0000000000..415ce7273d
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.h
@@ -0,0 +1,180 @@
+/* -*- 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_AccessibleOrProxy_h
+#define mozilla_a11y_AccessibleOrProxy_h
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/a11y/Role.h"
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * This class stores an Accessible* or a ProxyAccessible* in a safe manner
+ * with size sizeof(void*).
+ */
+class AccessibleOrProxy {
+ public:
+ MOZ_IMPLICIT AccessibleOrProxy(Accessible* aAcc)
+ : mBits(reinterpret_cast<uintptr_t>(aAcc)) {}
+ MOZ_IMPLICIT AccessibleOrProxy(ProxyAccessible* aProxy)
+ : mBits(aProxy ? (reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY) : 0) {}
+ MOZ_IMPLICIT AccessibleOrProxy(decltype(nullptr)) : mBits(0) {}
+ MOZ_IMPLICIT AccessibleOrProxy() : mBits(0) {}
+
+ bool IsProxy() const { return mBits & IS_PROXY; }
+ ProxyAccessible* AsProxy() const {
+ if (IsProxy()) {
+ return reinterpret_cast<ProxyAccessible*>(mBits & ~IS_PROXY);
+ }
+
+ return nullptr;
+ }
+
+ bool IsAccessible() const { return !IsProxy(); }
+ Accessible* AsAccessible() const {
+ if (IsAccessible()) {
+ return reinterpret_cast<Accessible*>(mBits);
+ }
+
+ return nullptr;
+ }
+
+ bool IsNull() const { return mBits == 0; }
+
+ uint32_t ChildCount() const {
+ if (IsProxy()) {
+ return AsProxy()->ChildrenCount();
+ }
+
+ if (RemoteChildDoc()) {
+ return 1;
+ }
+
+ return AsAccessible()->ChildCount();
+ }
+
+ /**
+ * Return true if the object has children, false otherwise
+ */
+ bool HasChildren() const { return ChildCount() > 0; }
+
+ /**
+ * Return the child object either an accessible or a proxied accessible at
+ * the given index.
+ */
+ AccessibleOrProxy ChildAt(uint32_t aIdx) const {
+ if (IsProxy()) {
+ return AsProxy()->ChildAt(aIdx);
+ }
+
+ ProxyAccessible* childDoc = RemoteChildDoc();
+ if (childDoc && aIdx == 0) {
+ return childDoc;
+ }
+
+ return AsAccessible()->GetChildAt(aIdx);
+ }
+
+ /**
+ * Return the first child object.
+ */
+ AccessibleOrProxy FirstChild() {
+ if (IsProxy()) {
+ return AsProxy()->FirstChild();
+ }
+
+ ProxyAccessible* childDoc = RemoteChildDoc();
+ if (childDoc) {
+ return childDoc;
+ }
+
+ return AsAccessible()->FirstChild();
+ }
+
+ /**
+ * Return the last child object.
+ */
+ AccessibleOrProxy LastChild() {
+ if (IsProxy()) {
+ return AsProxy()->LastChild();
+ }
+
+ ProxyAccessible* childDoc = RemoteChildDoc();
+ if (childDoc) {
+ return childDoc;
+ }
+
+ return AsAccessible()->LastChild();
+ }
+
+ /**
+ * Return the next sibling object.
+ */
+ AccessibleOrProxy NextSibling() {
+ if (IsProxy()) {
+ return AsProxy()->NextSibling();
+ }
+
+ return AsAccessible()->NextSibling();
+ }
+
+ /**
+ * Return the prev sibling object.
+ */
+ AccessibleOrProxy PrevSibling() {
+ if (IsProxy()) {
+ return AsProxy()->PrevSibling();
+ }
+
+ return AsAccessible()->PrevSibling();
+ }
+
+ role Role() const {
+ if (IsProxy()) {
+ return AsProxy()->Role();
+ }
+
+ return AsAccessible()->Role();
+ }
+
+ int32_t IndexInParent() const;
+
+ AccessibleOrProxy Parent() const;
+
+ AccessibleOrProxy ChildAtPoint(int32_t aX, int32_t aY,
+ Accessible::EWhichChildAtPoint aWhichChild);
+
+ bool operator!=(const AccessibleOrProxy& aOther) const {
+ return mBits != aOther.mBits;
+ }
+
+ bool operator==(const AccessibleOrProxy& aOther) const {
+ return mBits == aOther.mBits;
+ }
+
+ // XXX these are implementation details that ideally would not be exposed.
+ uintptr_t Bits() const { return mBits; }
+ void SetBits(uintptr_t aBits) { mBits = aBits; }
+
+ private:
+ /**
+ * If this is an OuterDocAccessible, return the remote child document.
+ */
+ ProxyAccessible* RemoteChildDoc() const;
+
+ uintptr_t mBits;
+ static const uintptr_t IS_PROXY = 0x1;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/Asserts.cpp b/accessible/base/Asserts.cpp
new file mode 100644
index 0000000000..0d7fb88c71
--- /dev/null
+++ b/accessible/base/Asserts.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "nsIAccessibleRelation.h"
+#include "nsIAccessibleRole.h"
+#include "RelationType.h"
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ static_assert( \
+ static_cast<uint32_t>(roles::geckoRole) == \
+ static_cast<uint32_t>(nsIAccessibleRole::ROLE_##geckoRole), \
+ "internal and xpcom roles differ!");
+#include "RoleMap.h"
+#undef ROLE
+
+#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
+ static_assert( \
+ static_cast<uint32_t>(RelationType::geckoType) == \
+ static_cast<uint32_t>(nsIAccessibleRelation::RELATION_##geckoType), \
+ "internal and xpcom relations differ!");
+#include "RelationTypeMap.h"
+#undef RELATIONTYPE
diff --git a/accessible/base/DocManager.cpp b/accessible/base/DocManager.cpp
new file mode 100644
index 0000000000..9011f20dad
--- /dev/null
+++ b/accessible/base/DocManager.cpp
@@ -0,0 +1,562 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "DocManager.h"
+
+#include "ApplicationAccessible.h"
+#include "ARIAMap.h"
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "DocAccessibleParent.h"
+#include "nsAccessibilityService.h"
+#include "Platform.h"
+#include "RootAccessibleWrap.h"
+
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+
+#include "mozilla/Components.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "nsContentUtils.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgress.h"
+#include "nsCoreUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "xpcAccessibleDocument.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
+StaticAutoPtr<nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>,
+ xpcAccessibleDocument>>
+ DocManager::sRemoteXPCDocumentCache;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager
+////////////////////////////////////////////////////////////////////////////////
+
+DocManager::DocManager() : mDocAccessibleCache(2), mXPCDocumentCache(0) {}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager public
+
+DocAccessible* DocManager::GetDocAccessible(Document* aDocument) {
+ if (!aDocument) return nullptr;
+
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (docAcc) return docAcc;
+
+ return CreateDocOrRootAccessible(aDocument);
+}
+
+DocAccessible* DocManager::GetDocAccessible(const PresShell* aPresShell) {
+ if (!aPresShell) {
+ return nullptr;
+ }
+
+ DocAccessible* doc = aPresShell->GetDocAccessible();
+ if (doc) {
+ return doc;
+ }
+
+ return GetDocAccessible(aPresShell->GetDocument());
+}
+
+Accessible* DocManager::FindAccessibleInCache(nsINode* aNode) const {
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible) {
+ Accessible* accessible = docAccessible->GetAccessible(aNode);
+ if (accessible) {
+ return accessible;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void DocManager::RemoveFromXPCDocumentCache(DocAccessible* aDocument) {
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ mXPCDocumentCache.Remove(aDocument);
+
+ if (!HasXPCDocuments()) {
+ MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
+ }
+ }
+}
+
+void DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ Document* aDOMDocument) {
+ // We need to remove listeners in both cases, when document is being shutdown
+ // or when accessibility service is being shut down as well.
+ RemoveListeners(aDOMDocument);
+
+ // Document will already be removed when accessibility service is shutting
+ // down so we do not need to remove it twice.
+ if (nsAccessibilityService::IsShutdown()) {
+ return;
+ }
+
+ RemoveFromXPCDocumentCache(aDocument);
+ mDocAccessibleCache.Remove(aDOMDocument);
+}
+
+void DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc) {
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ doc->Shutdown();
+ sRemoteXPCDocumentCache->Remove(aDoc);
+ }
+
+ if (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() == 0) {
+ MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
+ }
+}
+
+void DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc) {
+ RemoveFromRemoteXPCDocumentCache(aDoc);
+}
+
+xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessible* aDocument) {
+ if (!aDocument) return nullptr;
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (!xpcDoc) {
+ xpcDoc = new xpcAccessibleDocument(aDocument);
+ mXPCDocumentCache.Put(aDocument, RefPtr{xpcDoc});
+ }
+ return xpcDoc;
+}
+
+xpcAccessibleDocument* DocManager::GetXPCDocument(DocAccessibleParent* aDoc) {
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ return doc;
+ }
+
+ if (!sRemoteXPCDocumentCache) {
+ sRemoteXPCDocumentCache =
+ new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>,
+ xpcAccessibleDocument>;
+ ClearOnShutdown(&sRemoteXPCDocumentCache);
+ }
+
+ MOZ_ASSERT(!aDoc->IsShutdown(), "Adding a shutdown doc to remote XPC cache");
+ doc = new xpcAccessibleDocument(aDoc,
+ Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+ sRemoteXPCDocumentCache->Put(aDoc, RefPtr{doc});
+
+ return doc;
+}
+
+#ifdef DEBUG
+bool DocManager::IsProcessingRefreshDriverNotification() const {
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible && docAccessible->mNotificationController &&
+ docAccessible->mNotificationController->IsUpdating()) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager protected
+
+bool DocManager::Init() {
+ nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
+
+ if (!progress) return false;
+
+ progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ return true;
+}
+
+void DocManager::Shutdown() {
+ nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service();
+
+ if (progress)
+ progress->RemoveProgressListener(
+ static_cast<nsIWebProgressListener*>(this));
+
+ ClearDocCache();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS(DocManager, nsIWebProgressListener, nsIDOMEventListener,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+DocManager::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) {
+ NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
+
+ if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
+ (aStateFlags & (STATE_START | STATE_STOP)) == 0)
+ return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ NS_ENSURE_STATE(DOMWindow);
+
+ nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow);
+ MOZ_ASSERT(piWindow);
+
+ nsCOMPtr<Document> document = piWindow->GetDoc();
+ NS_ENSURE_STATE(document);
+
+ // Document was loaded.
+ if (aStateFlags & STATE_STOP) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
+#endif
+
+ // Figure out an event type to notify the document has been loaded.
+ uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
+
+ // Some XUL documents get start state and then stop state with failure
+ // status when everything is ok. Fire document load complete event in this
+ // case.
+ if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
+ eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
+
+ // If end consumer has been retargeted for loaded content then do not fire
+ // any event because it means no new document has been loaded, for example,
+ // it happens when user clicks on file link.
+ if (aRequest) {
+ uint32_t loadFlags = 0;
+ aRequest->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) eventType = 0;
+ }
+
+ HandleDOMDocumentLoad(document, eventType);
+ return NS_OK;
+ }
+
+ // Document loading was started.
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("start document loading", aWebProgress, aRequest,
+ aStateFlags);
+#endif
+
+ DocAccessible* docAcc = GetExistingDocAccessible(document);
+ if (!docAcc) return NS_OK;
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
+ NS_ENSURE_STATE(docShell);
+
+ bool isReloading = false;
+ uint32_t loadType;
+ docShell->GetLoadType(&loadType);
+ if (loadType == LOAD_RELOAD_NORMAL || loadType == LOAD_RELOAD_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
+ loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+ isReloading = true;
+ }
+
+ docAcc->NotifyOfLoading(isReloading);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+DocManager::HandleEvent(Event* aEvent) {
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ nsCOMPtr<Document> document = do_QueryInterface(aEvent->GetTarget());
+ NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
+ if (!document) return NS_OK;
+
+ if (type.EqualsLiteral("pagehide")) {
+ // 'pagehide' event is registered on every DOM document we create an
+ // accessible for, process the event for the target. This document
+ // accessible and all its sub document accessible are shutdown as result of
+ // processing.
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::DocDestroy("received 'pagehide' event", document);
+#endif
+
+ // Shutdown this one and sub document accessibles.
+
+ // We're allowed to not remove listeners when accessible document is
+ // shutdown since we don't keep strong reference on chrome event target and
+ // listeners are removed automatically when chrome event target goes away.
+ DocAccessible* docAccessible = GetExistingDocAccessible(document);
+ if (docAccessible) docAccessible->Shutdown();
+
+ return NS_OK;
+ }
+
+ // XXX: handle error pages loading separately since they get neither
+ // webprogress notifications nor 'pageshow' event.
+ if (type.EqualsLiteral("DOMContentLoaded") &&
+ nsCoreUtils::IsErrorPage(document)) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("handled 'DOMContentLoaded' event", document);
+#endif
+
+ HandleDOMDocumentLoad(document,
+ nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager private
+
+void DocManager::HandleDOMDocumentLoad(Document* aDocument,
+ uint32_t aLoadEventType) {
+ // Document accessible can be created before we were notified the DOM document
+ // was loaded completely. However if it's not created yet then create it.
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (!docAcc) {
+ docAcc = CreateDocOrRootAccessible(aDocument);
+ if (!docAcc) return;
+ }
+
+ docAcc->NotifyOfLoad(aLoadEventType);
+}
+
+void DocManager::AddListeners(Document* aDocument,
+ bool aAddDOMContentLoadedListener) {
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ EventTarget* target = window->GetChromeEventHandler();
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->AddEventListenerByType(this, u"pagehide"_ns, TrustedEventsAtCapture());
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'pagehide' listener");
+#endif
+
+ if (aAddDOMContentLoadedListener) {
+ elm->AddEventListenerByType(this, u"DOMContentLoaded"_ns,
+ TrustedEventsAtCapture());
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'DOMContentLoaded' listener");
+#endif
+ }
+}
+
+void DocManager::RemoveListeners(Document* aDocument) {
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ if (!window) return;
+
+ EventTarget* target = window->GetChromeEventHandler();
+ if (!target) return;
+
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->RemoveEventListenerByType(this, u"pagehide"_ns,
+ TrustedEventsAtCapture());
+
+ elm->RemoveEventListenerByType(this, u"DOMContentLoaded"_ns,
+ TrustedEventsAtCapture());
+}
+
+DocAccessible* DocManager::CreateDocOrRootAccessible(Document* aDocument) {
+ // Ignore hidden documents, resource documents, static clone
+ // (printing) documents and documents without a docshell.
+ if (!aDocument->IsVisibleConsideringAncestors() ||
+ aDocument->IsResourceDoc() || aDocument->IsStaticDocument() ||
+ !aDocument->IsActive()) {
+ return nullptr;
+ }
+
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (!docShell || docShell->IsInvisible()) {
+ return nullptr;
+ }
+
+ nsIWidget* widget = nsContentUtils::WidgetForDocument(aDocument);
+ if (!widget || widget->WindowType() == eWindowType_invisible) {
+ return nullptr;
+ }
+
+ // Ignore documents without presshell and not having root frame.
+ PresShell* presShell = aDocument->GetPresShell();
+ if (!presShell || presShell->IsDestroying()) {
+ return nullptr;
+ }
+
+ bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
+
+ DocAccessible* parentDocAcc = nullptr;
+ if (!isRootDoc) {
+ // XXXaaronl: ideally we would traverse the presshell chain. Since there's
+ // no easy way to do that, we cheat and use the document hierarchy.
+ parentDocAcc = GetDocAccessible(aDocument->GetInProcessParentDocument());
+ NS_ASSERTION(parentDocAcc, "Can't create an accessible for the document!");
+ if (!parentDocAcc) return nullptr;
+ }
+
+ // We only create root accessibles for the true root, otherwise create a
+ // doc accessible.
+ RefPtr<DocAccessible> docAcc =
+ isRootDoc ? new RootAccessibleWrap(aDocument, presShell)
+ : new DocAccessibleWrap(aDocument, presShell);
+
+ // Cache the document accessible into document cache.
+ mDocAccessibleCache.Put(aDocument, RefPtr{docAcc});
+
+ // Initialize the document accessible.
+ docAcc->Init();
+
+ // Bind the document to the tree.
+ if (isRootDoc) {
+ if (!ApplicationAcc()->AppendChild(docAcc)) {
+ docAcc->Shutdown();
+ return nullptr;
+ }
+
+ // Fire reorder event to notify new accessible document has been attached to
+ // the tree. The reorder event is delivered after the document tree is
+ // constructed because event processing and tree construction are done by
+ // the same document.
+ // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
+ // events processing.
+ docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
+ ApplicationAcc());
+
+ } else {
+ parentDocAcc->BindChildDocument(docAcc);
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate)) {
+ logging::DocCreate("document creation finished", aDocument);
+ logging::Stack();
+ }
+#endif
+
+ AddListeners(aDocument, isRootDoc);
+ return docAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager static
+
+void DocManager::ClearDocCache() {
+ while (mDocAccessibleCache.Count() > 0) {
+ auto iter = mDocAccessibleCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ DocAccessible* docAcc = iter.UserData();
+ NS_ASSERTION(docAcc,
+ "No doc accessible for the object in doc accessible cache!");
+ if (docAcc) {
+ docAcc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+
+ // Ensure that all xpcom accessible documents are shut down as well.
+ while (mXPCDocumentCache.Count() > 0) {
+ auto iter = mXPCDocumentCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ xpcAccessibleDocument* xpcDoc = iter.UserData();
+ NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
+
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+}
+
+void DocManager::RemoteDocAdded(DocAccessibleParent* aDoc) {
+ if (!sRemoteDocuments) {
+ sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
+ ClearOnShutdown(&sRemoteDocuments);
+ }
+
+ MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
+ "How did we already have the doc!");
+ sRemoteDocuments->AppendElement(aDoc);
+ ProxyCreated(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+}
+
+DocAccessible* mozilla::a11y::GetExistingDocAccessible(
+ const dom::Document* aDocument) {
+ PresShell* presShell = aDocument->GetPresShell();
+ return presShell ? presShell->GetDocAccessible() : nullptr;
+}
diff --git a/accessible/base/DocManager.h b/accessible/base/DocManager.h
new file mode 100644
index 0000000000..5fd820ff46
--- /dev/null
+++ b/accessible/base/DocManager.h
@@ -0,0 +1,189 @@
+/* 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_a11_DocManager_h_
+#define mozilla_a11_DocManager_h_
+
+#include "mozilla/ClearOnShutdown.h"
+#include "nsIDOMEventListener.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "mozilla/StaticPtr.h"
+#include "nsINode.h"
+
+namespace mozilla::dom {
+class Document;
+}
+
+namespace mozilla {
+class PresShell;
+
+namespace a11y {
+
+class Accessible;
+class DocAccessible;
+class xpcAccessibleDocument;
+class DocAccessibleParent;
+
+/**
+ * Manage the document accessible life cycle.
+ */
+class DocManager : public nsIWebProgressListener,
+ public nsIDOMEventListener,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ /**
+ * Return document accessible for the given DOM node.
+ */
+ DocAccessible* GetDocAccessible(dom::Document* aDocument);
+
+ /**
+ * Return document accessible for the given presshell.
+ */
+ DocAccessible* GetDocAccessible(const PresShell* aPresShell);
+
+ /**
+ * Search through all document accessibles for an accessible with the given
+ * unique id.
+ */
+ Accessible* FindAccessibleInCache(nsINode* aNode) const;
+
+ /**
+ * Called by document accessible when it gets shutdown.
+ */
+ void NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ dom::Document* aDOMDocument);
+
+ void RemoveFromXPCDocumentCache(DocAccessible* aDocument);
+
+ /**
+ * Return XPCOM accessible document.
+ */
+ xpcAccessibleDocument* GetXPCDocument(DocAccessible* aDocument);
+ xpcAccessibleDocument* GetCachedXPCDocument(DocAccessible* aDocument) const {
+ return mXPCDocumentCache.GetWeak(aDocument);
+ }
+
+ /*
+ * Notification that a top level document in a content process has gone away.
+ */
+ static void RemoteDocShutdown(DocAccessibleParent* aDoc) {
+ DebugOnly<bool> result = sRemoteDocuments->RemoveElement(aDoc);
+ MOZ_ASSERT(result, "Why didn't we find the document!");
+ }
+
+ /*
+ * Notify of a new top level document in a content process.
+ */
+ static void RemoteDocAdded(DocAccessibleParent* aDoc);
+
+ static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs() {
+ return sRemoteDocuments;
+ }
+
+ /**
+ * Remove the xpc document for a remote document if there is one.
+ */
+ static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
+
+ static void RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc);
+
+ /**
+ * Get a XPC document for a remote document.
+ */
+ static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
+ static xpcAccessibleDocument* GetCachedXPCDocument(
+ const DocAccessibleParent* aDoc) {
+ return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
+ : nullptr;
+ }
+
+#ifdef DEBUG
+ bool IsProcessingRefreshDriverNotification() const;
+#endif
+
+ protected:
+ DocManager();
+ virtual ~DocManager() = default;
+
+ /**
+ * Initialize the manager.
+ */
+ bool Init();
+
+ /**
+ * Shutdown the manager.
+ */
+ void Shutdown();
+
+ bool HasXPCDocuments() {
+ return mXPCDocumentCache.Count() > 0 ||
+ (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() > 0);
+ }
+
+ private:
+ DocManager(const DocManager&);
+ DocManager& operator=(const DocManager&);
+
+ private:
+ /**
+ * Create an accessible document if it was't created and fire accessibility
+ * events if needed.
+ *
+ * @param aDocument [in] loaded DOM document
+ * @param aLoadEventType [in] specifies the event type to fire load event,
+ * if 0 then no event is fired
+ */
+ void HandleDOMDocumentLoad(dom::Document* aDocument, uint32_t aLoadEventType);
+
+ /**
+ * Add/remove 'pagehide' and 'DOMContentLoaded' event listeners.
+ */
+ void AddListeners(dom::Document* aDocument, bool aAddPageShowListener);
+ void RemoveListeners(dom::Document* aDocument);
+
+ /**
+ * Create document or root accessible.
+ */
+ DocAccessible* CreateDocOrRootAccessible(dom::Document* aDocument);
+
+ /**
+ * Clear the cache and shutdown the document accessibles.
+ */
+ void ClearDocCache();
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const dom::Document>, DocAccessible>
+ DocAccessibleHashtable;
+ DocAccessibleHashtable mDocAccessibleCache;
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>,
+ xpcAccessibleDocument>
+ XPCDocumentHashtable;
+ XPCDocumentHashtable mXPCDocumentCache;
+ static StaticAutoPtr<nsRefPtrHashtable<
+ nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>>
+ sRemoteXPCDocumentCache;
+
+ /*
+ * The list of remote top level documents.
+ */
+ static StaticAutoPtr<nsTArray<DocAccessibleParent*>> sRemoteDocuments;
+};
+
+/**
+ * Return the existing document accessible for the document if any.
+ * Note this returns the doc accessible for the primary pres shell if there is
+ * more than one.
+ */
+DocAccessible* GetExistingDocAccessible(const dom::Document* aDocument);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11_DocManager_h_
diff --git a/accessible/base/EmbeddedObjCollector.cpp b/accessible/base/EmbeddedObjCollector.cpp
new file mode 100644
index 0000000000..96d0038d8b
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.cpp
@@ -0,0 +1,63 @@
+/* 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 "EmbeddedObjCollector.h"
+
+#include "Accessible.h"
+
+using namespace mozilla::a11y;
+
+uint32_t EmbeddedObjCollector::Count() {
+ EnsureNGetIndex(nullptr);
+ return mObjects.Length();
+}
+
+Accessible* EmbeddedObjCollector::GetAccessibleAt(uint32_t aIndex) {
+ Accessible* accessible = mObjects.SafeElementAt(aIndex, nullptr);
+ if (accessible) return accessible;
+
+ return EnsureNGetObject(aIndex);
+}
+
+Accessible* EmbeddedObjCollector::EnsureNGetObject(uint32_t aIndex) {
+ uint32_t childCount = mRoot->ChildCount();
+ while (mRootChildIdx < childCount) {
+ Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
+ if (child->IsText()) continue;
+
+ AppendObject(child);
+ if (mObjects.Length() - 1 == aIndex) return mObjects[aIndex];
+ }
+
+ return nullptr;
+}
+
+int32_t EmbeddedObjCollector::EnsureNGetIndex(Accessible* aAccessible) {
+ uint32_t childCount = mRoot->ChildCount();
+ while (mRootChildIdx < childCount) {
+ Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
+ if (child->IsText()) continue;
+
+ AppendObject(child);
+ if (child == aAccessible) return mObjects.Length() - 1;
+ }
+
+ return -1;
+}
+
+int32_t EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible) {
+ if (aAccessible->mParent != mRoot) return -1;
+
+ MOZ_ASSERT(!aAccessible->IsProxy());
+ if (aAccessible->mInt.mIndexOfEmbeddedChild != -1)
+ return aAccessible->mInt.mIndexOfEmbeddedChild;
+
+ return !aAccessible->IsText() ? EnsureNGetIndex(aAccessible) : -1;
+}
+
+void EmbeddedObjCollector::AppendObject(Accessible* aAccessible) {
+ MOZ_ASSERT(!aAccessible->IsProxy());
+ aAccessible->mInt.mIndexOfEmbeddedChild = mObjects.Length();
+ mObjects.AppendElement(aAccessible);
+}
diff --git a/accessible/base/EmbeddedObjCollector.h b/accessible/base/EmbeddedObjCollector.h
new file mode 100644
index 0000000000..3b18552795
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.h
@@ -0,0 +1,68 @@
+/* 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_EmbeddedObjCollector_h__
+#define mozilla_a11y_EmbeddedObjCollector_h__
+
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * Collect embedded objects. Provide quick access to accessible by index and
+ * vice versa.
+ */
+class EmbeddedObjCollector final {
+ public:
+ ~EmbeddedObjCollector() {}
+
+ /**
+ * Return index of the given accessible within the collection.
+ */
+ int32_t GetIndexAt(Accessible* aAccessible);
+
+ /**
+ * Return accessible count within the collection.
+ */
+ uint32_t Count();
+
+ /**
+ * Return an accessible from the collection at the given index.
+ */
+ Accessible* GetAccessibleAt(uint32_t aIndex);
+
+ protected:
+ /**
+ * Ensure accessible at the given index is stored and return it.
+ */
+ Accessible* EnsureNGetObject(uint32_t aIndex);
+
+ /**
+ * Ensure index for the given accessible is stored and return it.
+ */
+ int32_t EnsureNGetIndex(Accessible* aAccessible);
+
+ // Make sure it's used by Accessible class only.
+ explicit EmbeddedObjCollector(Accessible* aRoot)
+ : mRoot(aRoot), mRootChildIdx(0) {}
+
+ /**
+ * Append the object to collection.
+ */
+ void AppendObject(Accessible* aAccessible);
+
+ friend class Accessible;
+
+ Accessible* mRoot;
+ uint32_t mRootChildIdx;
+ nsTArray<Accessible*> mObjects;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/EventQueue.cpp b/accessible/base/EventQueue.cpp
new file mode 100644
index 0000000000..638b2c5b30
--- /dev/null
+++ b/accessible/base/EventQueue.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventQueue.h"
+
+#include "Accessible-inl.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#include "DocAccessibleChild.h"
+#include "nsAccessibilityService.h"
+#include "nsTextEquivUtils.h"
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+// Defines the number of selection add/remove events in the queue when they
+// aren't packed into single selection within event.
+const unsigned int kSelChangeCountToPack = 5;
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue
+////////////////////////////////////////////////////////////////////////////////
+
+bool EventQueue::PushEvent(AccEvent* aEvent) {
+ NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
+ aEvent->Document() == mDocument,
+ "Queued event belongs to another document!");
+
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mEvents.AppendElement(aEvent);
+
+ // Filter events.
+ CoalesceEvents();
+
+ if (aEvent->mEventRule != AccEvent::eDoNotEmit &&
+ (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
+ aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
+ aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) {
+ PushNameChange(aEvent->mAccessible);
+ }
+ return true;
+}
+
+bool EventQueue::PushNameChange(Accessible* aTarget) {
+ // Fire name change event on parent given that this event hasn't been
+ // coalesced, the parent's name was calculated from its subtree, and the
+ // subtree was changed.
+ if (aTarget->HasNameDependentParent()) {
+ // Only continue traversing up the tree if it's possible that the parent
+ // accessible's name can depend on this accessible's name.
+ Accessible* parent = aTarget->Parent();
+ while (parent &&
+ nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
+ // Test possible name dependent parent.
+ if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
+ nsAutoString name;
+ ENameValueFlag nameFlag = parent->Name(name);
+ // If name is obtained from subtree, fire name change event.
+ if (nameFlag == eNameFromSubtree) {
+ RefPtr<AccEvent> nameChangeEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
+ return PushEvent(nameChangeEvent);
+ }
+ break;
+ }
+ parent = parent->Parent();
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue: private
+
+void EventQueue::CoalesceEvents() {
+ NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
+ uint32_t tail = mEvents.Length() - 1;
+ AccEvent* tailEvent = mEvents[tail];
+
+ switch (tailEvent->mEventRule) {
+ case AccEvent::eCoalesceReorder: {
+ DebugOnly<Accessible*> target = tailEvent->mAccessible.get();
+ MOZ_ASSERT(
+ target->IsApplication() || target->IsOuterDoc() ||
+ target->IsXULTree(),
+ "Only app or outerdoc accessible reorder events are in the queue");
+ MOZ_ASSERT(tailEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER,
+ "only reorder events should be queued");
+ break; // case eCoalesceReorder
+ }
+
+ case AccEvent::eCoalesceOfSameType: {
+ // Coalesce old events by newer event.
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* accEvent = mEvents[index];
+ if (accEvent->mEventType == tailEvent->mEventType &&
+ accEvent->mEventRule == tailEvent->mEventRule) {
+ accEvent->mEventRule = AccEvent::eDoNotEmit;
+ return;
+ }
+ }
+ break; // case eCoalesceOfSameType
+ }
+
+ case AccEvent::eCoalesceSelectionChange: {
+ AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule == tailEvent->mEventRule) {
+ AccSelChangeEvent* thisSelChangeEvent = downcast_accEvent(thisEvent);
+
+ // Coalesce selection change events within same control.
+ if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
+ CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent,
+ index);
+ return;
+ }
+ }
+ }
+ break; // eCoalesceSelectionChange
+ }
+
+ case AccEvent::eCoalesceStateChange: {
+ // If state change event is duped then ignore previous event. If state
+ // change event is opposite to previous event then no event is emitted
+ // (accessible state wasn't changed).
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
+ thisEvent->mEventType == tailEvent->mEventType &&
+ thisEvent->mAccessible == tailEvent->mAccessible) {
+ AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
+ AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
+ if (thisSCEvent->mState == tailSCEvent->mState) {
+ thisEvent->mEventRule = AccEvent::eDoNotEmit;
+ if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
+ tailEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+ }
+ }
+ break; // eCoalesceStateChange
+ }
+
+ case AccEvent::eCoalesceTextSelChange: {
+ // Coalesce older event by newer event for the same selection or target.
+ // Events for same selection may have different targets and vice versa one
+ // target may be pointed by different selections (for latter see
+ // bug 927159).
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
+ thisEvent->mEventType == tailEvent->mEventType) {
+ AccTextSelChangeEvent* thisTSCEvent = downcast_accEvent(thisEvent);
+ AccTextSelChangeEvent* tailTSCEvent = downcast_accEvent(tailEvent);
+ if (thisTSCEvent->mSel == tailTSCEvent->mSel ||
+ thisEvent->mAccessible == tailEvent->mAccessible)
+ thisEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+ }
+ break; // eCoalesceTextSelChange
+ }
+
+ case AccEvent::eRemoveDupes: {
+ // Check for repeat events, coalesce newly appended event by more older
+ // event.
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* accEvent = mEvents[index];
+ if (accEvent->mEventType == tailEvent->mEventType &&
+ accEvent->mEventRule == tailEvent->mEventRule &&
+ accEvent->mAccessible == tailEvent->mAccessible) {
+ tailEvent->mEventRule = AccEvent::eDoNotEmit;
+ return;
+ }
+ }
+ break; // case eRemoveDupes
+ }
+
+ default:
+ break; // case eAllowDupes, eDoNotEmit
+ } // switch
+}
+
+void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+ AccSelChangeEvent* aThisEvent,
+ uint32_t aThisIndex) {
+ aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
+
+ // Pack all preceding events into single selection within event
+ // when we receive too much selection add/remove events.
+ if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
+ aTailEvent->mAccessible = aTailEvent->mWidget;
+ aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+
+ // Do not emit any preceding selection events for same widget if they
+ // weren't coalesced yet.
+ if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
+ for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
+ AccEvent* prevEvent = mEvents[jdx];
+ if (prevEvent->mEventRule == aTailEvent->mEventRule) {
+ AccSelChangeEvent* prevSelChangeEvent = downcast_accEvent(prevEvent);
+ if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
+ prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+ }
+ }
+ return;
+ }
+
+ // Pack sequential selection remove and selection add events into
+ // single selection change event.
+ if (aTailEvent->mPreceedingCount == 1 &&
+ aTailEvent->mItem != aThisEvent->mItem) {
+ if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+ aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+ aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ aTailEvent->mPackedEvent = aThisEvent;
+ return;
+ }
+
+ if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+ aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+ aTailEvent->mEventRule = AccEvent::eDoNotEmit;
+ aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ aThisEvent->mPackedEvent = aTailEvent;
+ return;
+ }
+ }
+
+ // Unpack the packed selection change event because we've got one
+ // more selection add/remove.
+ if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+ if (aThisEvent->mPackedEvent) {
+ aThisEvent->mPackedEvent->mEventType =
+ aThisEvent->mPackedEvent->mSelChangeType ==
+ AccSelChangeEvent::eSelectionAdd
+ ? nsIAccessibleEvent::EVENT_SELECTION_ADD
+ : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+ aThisEvent->mPackedEvent->mEventRule = AccEvent::eCoalesceSelectionChange;
+
+ aThisEvent->mPackedEvent = nullptr;
+ }
+
+ aThisEvent->mEventType =
+ aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd
+ ? nsIAccessibleEvent::EVENT_SELECTION_ADD
+ : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+ return;
+ }
+
+ // Convert into selection add since control has single selection but other
+ // selection events for this control are queued.
+ if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue: event queue
+
+void EventQueue::ProcessEventQueue() {
+ // Process only currently queued events.
+ const nsTArray<RefPtr<AccEvent> > events = std::move(mEvents);
+
+ uint32_t eventCount = events.Length();
+#ifdef A11Y_LOG
+ if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
+ logging::MsgBegin("EVENTS", "events processing");
+ logging::Address("document", mDocument);
+ logging::MsgEnd();
+ }
+#endif
+
+ for (uint32_t idx = 0; idx < eventCount; idx++) {
+ AccEvent* event = events[idx];
+ if (event->mEventRule != AccEvent::eDoNotEmit) {
+ Accessible* target = event->GetAccessible();
+ if (!target || target->IsDefunct()) continue;
+
+ // Dispatch the focus event if target is still focused.
+ if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
+ FocusMgr()->ProcessFocusEvent(event);
+ continue;
+ }
+
+ // Dispatch caret moved and text selection change events.
+ if (event->mEventType ==
+ nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
+ SelectionMgr()->ProcessTextSelChangeEvent(event);
+ continue;
+ }
+
+ // Fire selected state change events in support to selection events.
+ if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED, true,
+ event->mIsFromUserInput);
+
+ } else if (event->mEventType ==
+ nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED, false,
+ event->mIsFromUserInput);
+
+ } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+ AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+ (selChangeEvent->mSelChangeType ==
+ AccSelChangeEvent::eSelectionAdd),
+ event->mIsFromUserInput);
+
+ if (selChangeEvent->mPackedEvent) {
+ nsEventShell::FireEvent(
+ selChangeEvent->mPackedEvent->mAccessible, states::SELECTED,
+ (selChangeEvent->mPackedEvent->mSelChangeType ==
+ AccSelChangeEvent::eSelectionAdd),
+ selChangeEvent->mPackedEvent->mIsFromUserInput);
+ }
+ }
+
+ nsEventShell::FireEvent(event);
+ }
+
+ if (!mDocument) return;
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/EventQueue.h b/accessible/base/EventQueue.h
new file mode 100644
index 0000000000..dceaf2843a
--- /dev/null
+++ b/accessible/base/EventQueue.h
@@ -0,0 +1,76 @@
+/* -*- 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 mozilla_a11y_EventQueue_h_
+#define mozilla_a11y_EventQueue_h_
+
+#include "AccEvent.h"
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessible;
+
+/**
+ * Used to organize and coalesce pending events.
+ */
+class EventQueue {
+ protected:
+ explicit EventQueue(DocAccessible* aDocument) : mDocument(aDocument) {}
+
+ /**
+ * Put an accessible event into the queue to process it later.
+ */
+ bool PushEvent(AccEvent* aEvent);
+
+ /**
+ * Puts a name change event into the queue, if needed.
+ */
+ bool PushNameChange(Accessible* aTarget);
+
+ /**
+ * Process events from the queue and fires events.
+ */
+ void ProcessEventQueue();
+
+ private:
+ EventQueue(const EventQueue&) = delete;
+ EventQueue& operator=(const EventQueue&) = delete;
+
+ // Event queue processing
+ /**
+ * Coalesce redundant events from the queue.
+ */
+ void CoalesceEvents();
+
+ /**
+ * Coalesce events from the same subtree.
+ */
+ void CoalesceReorderEvents(AccEvent* aTailEvent);
+
+ /**
+ * Coalesce two selection change events within the same select control.
+ */
+ void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+ AccSelChangeEvent* aThisEvent,
+ uint32_t aThisIndex);
+
+ protected:
+ /**
+ * The document accessible reference owning this queue.
+ */
+ DocAccessible* mDocument;
+
+ /**
+ * Pending events array. Don't make this an AutoTArray; we use
+ * SwapElements() on it.
+ */
+ nsTArray<RefPtr<AccEvent>> mEvents;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_EventQueue_h_
diff --git a/accessible/base/EventTree.cpp b/accessible/base/EventTree.cpp
new file mode 100644
index 0000000000..f804275775
--- /dev/null
+++ b/accessible/base/EventTree.cpp
@@ -0,0 +1,601 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "EventTree.h"
+
+#include "Accessible-inl.h"
+#include "EmbeddedObjCollector.h"
+#include "NotificationController.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+
+#include "mozilla/UniquePtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TreeMutation class
+
+EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
+
+TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents)
+ : mParent(aParent),
+ mStartIdx(UINT32_MAX),
+ mStateFlagsCopy(mParent->mStateFlags),
+ mQueueEvents(!aNoEvents) {
+#ifdef DEBUG
+ mIsDone = false;
+#endif
+
+#ifdef A11Y_LOG
+ if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "reordering tree before");
+ logging::AccessibleInfo("reordering for", mParent);
+ Controller()->RootEventTree().Log();
+ logging::MsgEnd();
+
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("EVENTS_TREE", "Container tree", mParent->Document(),
+ PrefixLog, static_cast<void*>(this));
+ }
+ }
+#endif
+
+ mParent->mStateFlags |= Accessible::eKidsMutating;
+}
+
+TreeMutation::~TreeMutation() {
+ MOZ_ASSERT(mIsDone, "Done() must be called explicitly");
+}
+
+void TreeMutation::AfterInsertion(Accessible* aChild) {
+ MOZ_ASSERT(aChild->Parent() == mParent);
+
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+ mStartIdx = aChild->mIndexInParent + 1;
+ }
+
+ if (!mQueueEvents) {
+ return;
+ }
+
+ RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+ DebugOnly<bool> added = Controller()->QueueMutationEvent(ev);
+ MOZ_ASSERT(added);
+ aChild->SetShowEventTarget(true);
+}
+
+void TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown) {
+ MOZ_ASSERT(aChild->Parent() == mParent);
+
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+ mStartIdx = aChild->mIndexInParent;
+ }
+
+ if (!mQueueEvents) {
+ return;
+ }
+
+ RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, !aNoShutdown);
+ if (Controller()->QueueMutationEvent(ev)) {
+ aChild->SetHideEventTarget(true);
+ }
+}
+
+void TreeMutation::Done() {
+ MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating);
+ mParent->mStateFlags &= ~Accessible::eKidsMutating;
+
+ uint32_t length = mParent->mChildren.Length();
+#ifdef DEBUG
+ for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
+ MOZ_ASSERT(
+ mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
+ "Wrong index detected");
+ }
+#endif
+
+ for (uint32_t idx = mStartIdx; idx < length; idx++) {
+ mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
+ }
+
+ for (uint32_t idx = 0; idx < length; idx++) {
+ mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
+ }
+
+ mParent->mEmbeddedObjCollector = nullptr;
+ mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
+
+#ifdef DEBUG
+ mIsDone = true;
+#endif
+
+#ifdef A11Y_LOG
+ if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "reordering tree after");
+ logging::AccessibleInfo("reordering for", mParent);
+ Controller()->RootEventTree().Log();
+ logging::MsgEnd();
+ }
+#endif
+}
+
+#ifdef A11Y_LOG
+const char* TreeMutation::PrefixLog(void* aData, Accessible* aAcc) {
+ TreeMutation* thisObj = reinterpret_cast<TreeMutation*>(aData);
+ if (thisObj->mParent == aAcc) {
+ return "_X_";
+ }
+ const EventTree& ret = thisObj->Controller()->RootEventTree();
+ if (ret.Find(aAcc)) {
+ return "_с_";
+ }
+ return "";
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// EventTree
+
+void EventTree::Shown(Accessible* aChild) {
+ RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+ Controller(aChild)->WithdrawPrecedingEvents(&ev->mPrecedingEvents);
+ Mutated(ev);
+}
+
+void EventTree::Hidden(Accessible* aChild, bool aNeedsShutdown) {
+ RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
+ if (!aNeedsShutdown) {
+ Controller(aChild)->StorePrecedingEvent(ev);
+ }
+ Mutated(ev);
+}
+
+void EventTree::Process(const RefPtr<DocAccessible>& aDeathGrip) {
+ while (mFirst) {
+ // Skip a node and its subtree if its container is not in the document.
+ if (mFirst->mContainer->IsInDocument()) {
+ mFirst->Process(aDeathGrip);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+ mFirst = std::move(mFirst->mNext);
+ }
+
+ MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
+ "No container, no events");
+ MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
+ "Processing events for defunct container");
+ MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
+
+ // Fire mutation events.
+ uint32_t eventsCount = mDependentEvents.Length();
+ for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
+ AccMutationEvent* mtEvent = mDependentEvents[jdx];
+ MOZ_ASSERT(mtEvent->Document(), "No document for event target");
+
+ // Fire all hide events that has to be fired before this show event.
+ if (mtEvent->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(mtEvent);
+ for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+ nsEventShell::FireEvent(showEv->mPrecedingEvents[i]);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+ }
+
+ nsEventShell::FireEvent(mtEvent);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+
+ if (mtEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+
+ if (mtEvent->IsHide()) {
+ // Fire menupopup end event before a hide event if a menu goes away.
+
+ // XXX: We don't look into children of hidden subtree to find hiding
+ // menupopup (as we did prior bug 570275) because we don't do that when
+ // menu is showing (and that's impossible until bug 606924 is fixed).
+ // Nevertheless we should do this at least because layout coalesces
+ // the changes before our processing and we may miss some menupopup
+ // events. Now we just want to be consistent in content insertion/removal
+ // handling.
+ if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ mtEvent->mAccessible);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+
+ AccHideEvent* hideEvent = downcast_accEvent(mtEvent);
+ if (hideEvent->NeedsShutdown()) {
+ aDeathGrip->ShutdownChildrenInSubtree(mtEvent->mAccessible);
+ }
+ }
+ }
+
+ // Fire reorder event at last.
+ if (mFireReorder) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
+ mContainer->Document()->MaybeNotifyOfValueChange(mContainer);
+ }
+
+ mDependentEvents.Clear();
+}
+
+EventTree* EventTree::FindOrInsert(Accessible* aContainer) {
+ if (!mFirst) {
+ mFirst.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ return mFirst.get();
+ }
+
+ EventTree* prevNode = nullptr;
+ EventTree* node = mFirst.get();
+ do {
+ MOZ_ASSERT(!node->mContainer->IsApplication(),
+ "No event for application accessible is expected here");
+ MOZ_ASSERT(!node->mContainer->IsDefunct(),
+ "An event target has to be alive");
+
+ // Case of same target.
+ if (node->mContainer == aContainer) {
+ return node;
+ }
+
+ // Check if the given container is contained by a current node
+ Accessible* top = mContainer ? mContainer : aContainer->Document();
+ Accessible* parent = aContainer;
+ while (parent) {
+ // Reached a top, no match for a current event.
+ if (parent == top) {
+ break;
+ }
+
+ // We got a match.
+ if (parent->Parent() == node->mContainer) {
+ // Reject the node if it's contained by a show/hide event target
+ uint32_t evCount = node->mDependentEvents.Length();
+ for (uint32_t idx = 0; idx < evCount; idx++) {
+ AccMutationEvent* ev = node->mDependentEvents[idx];
+ if (ev->GetAccessible() == parent) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE",
+ "Rejecting node contained by show/hide");
+ logging::AccessibleInfo("Node", aContainer);
+ logging::MsgEnd();
+ }
+#endif
+ // If the node is rejected, then check if it has related hide event
+ // on stack, and if so, then connect it to the parent show event.
+ if (ev->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(ev);
+ Controller(aContainer)
+ ->WithdrawPrecedingEvents(&showEv->mPrecedingEvents);
+ }
+ return nullptr;
+ }
+ }
+
+ return node->FindOrInsert(aContainer);
+ }
+
+ parent = parent->Parent();
+ MOZ_ASSERT(parent, "Wrong tree");
+ }
+
+ // If the given container contains a current node
+ // then
+ // if show or hide of the given node contains a grand parent of the
+ // current node then ignore the current node and its show and hide events
+ // otherwise ignore the current node, but not its show and hide events
+ Accessible* curParent = node->mContainer;
+ while (curParent && !curParent->IsDoc()) {
+ if (curParent->Parent() != aContainer) {
+ curParent = curParent->Parent();
+ continue;
+ }
+
+ // Insert the tail node into the hierarchy between the current node and
+ // its parent.
+ node->mFireReorder = false;
+ UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
+ UniquePtr<EventTree> newNode(
+ new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ newNode->mFirst = std::move(nodeOwnerRef);
+ nodeOwnerRef = std::move(newNode);
+ nodeOwnerRef->mNext = std::move(node->mNext);
+
+ // Check if a next node is contained by the given node too, and move them
+ // under the given node if so.
+ prevNode = nodeOwnerRef.get();
+ node = nodeOwnerRef->mNext.get();
+ UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
+ EventTree* insNode = nodeOwnerRef->mFirst.get();
+ while (node) {
+ Accessible* curParent = node->mContainer;
+ while (curParent && !curParent->IsDoc()) {
+ if (curParent->Parent() != aContainer) {
+ curParent = curParent->Parent();
+ continue;
+ }
+
+ MOZ_ASSERT(!insNode->mNext);
+
+ node->mFireReorder = false;
+ insNode->mNext = std::move(*nodeRef);
+ insNode = insNode->mNext.get();
+
+ prevNode->mNext = std::move(node->mNext);
+ node = prevNode;
+ break;
+ }
+
+ prevNode = node;
+ nodeRef = &node->mNext;
+ node = node->mNext.get();
+ }
+
+ return nodeOwnerRef.get();
+ }
+
+ prevNode = node;
+ } while ((node = node->mNext.get()));
+
+ MOZ_ASSERT(prevNode, "Nowhere to insert");
+ MOZ_ASSERT(!prevNode->mNext, "Taken by another node");
+
+ // If 'this' node contains the given container accessible, then
+ // do not emit a reorder event for the container
+ // if a dependent show event target contains the given container then do not
+ // emit show / hide events (see Process() method)
+
+ prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ return prevNode->mNext.get();
+}
+
+void EventTree::Clear() {
+ mFirst = nullptr;
+ mNext = nullptr;
+ mContainer = nullptr;
+
+ uint32_t eventsCount = mDependentEvents.Length();
+ for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
+ mDependentEvents[jdx]->mEventType = AccEvent::eDoNotEmit;
+ AccHideEvent* ev = downcast_accEvent(mDependentEvents[jdx]);
+ if (ev && ev->NeedsShutdown()) {
+ ev->Document()->ShutdownChildrenInSubtree(ev->mAccessible);
+ }
+ }
+ mDependentEvents.Clear();
+}
+
+const EventTree* EventTree::Find(const Accessible* aContainer) const {
+ const EventTree* et = this;
+ while (et) {
+ if (et->mContainer == aContainer) {
+ return et;
+ }
+
+ if (et->mFirst) {
+ et = et->mFirst.get();
+ const EventTree* cet = et->Find(aContainer);
+ if (cet) {
+ return cet;
+ }
+ }
+
+ et = et->mNext.get();
+ const EventTree* cet = et->Find(aContainer);
+ if (cet) {
+ return cet;
+ }
+ }
+
+ return nullptr;
+}
+
+#ifdef A11Y_LOG
+void EventTree::Log(uint32_t aLevel) const {
+ if (aLevel == UINT32_MAX) {
+ if (mFirst) {
+ mFirst->Log(0);
+ }
+ return;
+ }
+
+ for (uint32_t i = 0; i < aLevel; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("container", mContainer);
+
+ for (uint32_t i = 0; i < mDependentEvents.Length(); i++) {
+ AccMutationEvent* ev = mDependentEvents[i];
+ if (ev->IsShow()) {
+ for (uint32_t i = 0; i < aLevel + 1; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("shown", ev->mAccessible);
+
+ AccShowEvent* showEv = downcast_accEvent(ev);
+ for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+ for (uint32_t j = 0; j < aLevel + 1; j++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("preceding",
+ showEv->mPrecedingEvents[i]->mAccessible);
+ }
+ } else {
+ for (uint32_t i = 0; i < aLevel + 1; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("hidden", ev->mAccessible);
+ }
+ }
+
+ if (mFirst) {
+ mFirst->Log(aLevel + 1);
+ }
+
+ if (mNext) {
+ mNext->Log(aLevel);
+ }
+}
+#endif
+
+void EventTree::Mutated(AccMutationEvent* aEv) {
+ // If shown or hidden node is a root of previously mutated subtree, then
+ // discard those subtree mutations as we are no longer interested in them.
+ UniquePtr<EventTree>* node = &mFirst;
+ while (*node) {
+ Accessible* cntr = (*node)->mContainer;
+ while (cntr != mContainer) {
+ if (cntr == aEv->mAccessible) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "Trim subtree");
+ logging::AccessibleInfo("Show/hide container", aEv->mAccessible);
+ logging::AccessibleInfo("Trimmed subtree root", (*node)->mContainer);
+ logging::MsgEnd();
+ }
+#endif
+
+ // If the new hide is part of a move and it contains existing child
+ // shows, then move preceding events from the child shows to the buffer,
+ // so the ongoing show event will pick them up.
+ if (aEv->IsHide()) {
+ AccHideEvent* hideEv = downcast_accEvent(aEv);
+ if (!hideEv->mNeedsShutdown) {
+ for (uint32_t i = 0; i < (*node)->mDependentEvents.Length(); i++) {
+ AccMutationEvent* childEv = (*node)->mDependentEvents[i];
+ if (childEv->IsShow()) {
+ AccShowEvent* childShowEv = downcast_accEvent(childEv);
+ if (childShowEv->mPrecedingEvents.Length() > 0) {
+ Controller(mContainer)
+ ->StorePrecedingEvents(
+ std::move(childShowEv->mPrecedingEvents));
+ }
+ }
+ }
+ }
+ }
+ // If the new show contains existing child shows, then move preceding
+ // events from the child shows to the new show.
+ else if (aEv->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(aEv);
+ for (uint32_t i = 0; (*node)->mDependentEvents.Length(); i++) {
+ AccMutationEvent* childEv = (*node)->mDependentEvents[i];
+ if (childEv->IsShow()) {
+ AccShowEvent* showChildEv = downcast_accEvent(childEv);
+ if (showChildEv->mPrecedingEvents.Length() > 0) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "Adopt preceding events");
+ logging::AccessibleInfo("Parent", aEv->mAccessible);
+ for (uint32_t j = 0;
+ j < showChildEv->mPrecedingEvents.Length(); j++) {
+ logging::AccessibleInfo(
+ "Adoptee",
+ showChildEv->mPrecedingEvents[i]->mAccessible);
+ }
+ logging::MsgEnd();
+ }
+#endif
+ showEv->mPrecedingEvents.AppendElements(
+ showChildEv->mPrecedingEvents);
+ }
+ }
+ }
+ }
+
+ *node = std::move((*node)->mNext);
+ break;
+ }
+ cntr = cntr->Parent();
+ }
+ if (cntr == aEv->mAccessible) {
+ continue;
+ }
+ node = &(*node)->mNext;
+ }
+
+ AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr);
+ mDependentEvents.AppendElement(aEv);
+
+ // Coalesce text change events from this hide/show event and the previous one.
+ if (prevEvent && aEv->mEventType == prevEvent->mEventType) {
+ if (aEv->IsHide()) {
+ // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
+ // affect the text within the hypertext.
+ AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+ if (prevTextEvent) {
+ AccHideEvent* hideEvent = downcast_accEvent(aEv);
+ AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent);
+
+ if (prevHideEvent->mNextSibling == hideEvent->mAccessible) {
+ hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ } else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) {
+ uint32_t oldLen = prevTextEvent->GetLength();
+ hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen;
+ }
+
+ hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+ }
+ } else {
+ AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+ if (prevTextEvent) {
+ if (aEv->mAccessible->IndexInParent() ==
+ prevEvent->mAccessible->IndexInParent() + 1) {
+ // If tail target was inserted after this target, i.e. tail target is
+ // next sibling of this target.
+ aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ } else if (aEv->mAccessible->IndexInParent() ==
+ prevEvent->mAccessible->IndexInParent() - 1) {
+ // If tail target was inserted before this target, i.e. tail target is
+ // previous sibling of this target.
+ nsAutoString startText;
+ aEv->mAccessible->AppendTextTo(startText);
+ prevTextEvent->mModifiedText =
+ startText + prevTextEvent->mModifiedText;
+ prevTextEvent->mStart -= startText.Length();
+ }
+
+ aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+ }
+ }
+ }
+
+ // Create a text change event caused by this hide/show event. When a node is
+ // hidden/removed or shown/appended, the text in an ancestor hyper text will
+ // lose or get new characters.
+ if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) {
+ return;
+ }
+
+ nsAutoString text;
+ aEv->mAccessible->AppendTextTo(text);
+ if (text.IsEmpty()) {
+ return;
+ }
+
+ int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible);
+ aEv->mTextChangeEvent = new AccTextChangeEvent(
+ mContainer, offset, text, aEv->IsShow(),
+ aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput);
+}
diff --git a/accessible/base/EventTree.h b/accessible/base/EventTree.h
new file mode 100644
index 0000000000..a6efd81b79
--- /dev/null
+++ b/accessible/base/EventTree.h
@@ -0,0 +1,128 @@
+/* -*- 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 mozilla_a11y_EventTree_h_
+#define mozilla_a11y_EventTree_h_
+
+#include "AccEvent.h"
+#include "Accessible.h"
+
+#include "mozilla/a11y/DocAccessible.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+class NotificationController;
+
+/**
+ * This class makes sure required tasks are done before and after tree
+ * mutations. Currently this only includes group info invalidation. You must
+ * have an object of this class on the stack when calling methods that mutate
+ * the accessible tree.
+ */
+class TreeMutation final {
+ public:
+ static const bool kNoEvents = true;
+ static const bool kNoShutdown = true;
+
+ explicit TreeMutation(Accessible* aParent, bool aNoEvents = false);
+ ~TreeMutation();
+
+ void AfterInsertion(Accessible* aChild);
+ void BeforeRemoval(Accessible* aChild, bool aNoShutdown = false);
+ void Done();
+
+ private:
+ NotificationController* Controller() const {
+ return mParent->Document()->Controller();
+ }
+
+ static EventTree* const kNoEventTree;
+
+#ifdef A11Y_LOG
+ static const char* PrefixLog(void* aData, Accessible*);
+#endif
+
+ Accessible* mParent;
+ uint32_t mStartIdx;
+ uint32_t mStateFlagsCopy;
+
+ /*
+ * True if mutation events should be queued.
+ */
+ bool mQueueEvents;
+
+#ifdef DEBUG
+ bool mIsDone;
+#endif
+};
+
+/**
+ * A mutation events coalescence structure.
+ */
+class EventTree final {
+ public:
+ EventTree()
+ : mFirst(nullptr),
+ mNext(nullptr),
+ mContainer(nullptr),
+ mFireReorder(false) {}
+ explicit EventTree(Accessible* aContainer, bool aFireReorder)
+ : mFirst(nullptr),
+ mNext(nullptr),
+ mContainer(aContainer),
+ mFireReorder(aFireReorder) {}
+ ~EventTree() { Clear(); }
+
+ void Shown(Accessible* aTarget);
+ void Hidden(Accessible*, bool);
+
+ /**
+ * Return an event tree node for the given accessible.
+ */
+ const EventTree* Find(const Accessible* aContainer) const;
+
+ /**
+ * Add a mutation event to this event tree.
+ */
+ void Mutated(AccMutationEvent* aEv);
+
+#ifdef A11Y_LOG
+ void Log(uint32_t aLevel = UINT32_MAX) const;
+#endif
+
+ private:
+ /**
+ * Processes the event queue and fires events.
+ */
+ void Process(const RefPtr<DocAccessible>& aDeathGrip);
+
+ /**
+ * Return an event subtree for the given accessible.
+ */
+ EventTree* FindOrInsert(Accessible* aContainer);
+
+ void Clear();
+
+ UniquePtr<EventTree> mFirst;
+ UniquePtr<EventTree> mNext;
+
+ Accessible* mContainer;
+ nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
+ bool mFireReorder;
+
+ static NotificationController* Controller(Accessible* aAcc) {
+ return aAcc->Document()->Controller();
+ }
+
+ friend class NotificationController;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_EventQueue_h_
diff --git a/accessible/base/Filters.cpp b/accessible/base/Filters.cpp
new file mode 100644
index 0000000000..e0d55113e5
--- /dev/null
+++ b/accessible/base/Filters.cpp
@@ -0,0 +1,44 @@
+/* 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 "Filters.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::filters;
+
+uint32_t filters::GetSelected(Accessible* aAccessible) {
+ if (aAccessible->State() & states::SELECTED) return eMatch | eSkipSubtree;
+
+ return eSkip;
+}
+
+uint32_t filters::GetSelectable(Accessible* aAccessible) {
+ if (aAccessible->InteractiveState() & states::SELECTABLE)
+ return eMatch | eSkipSubtree;
+
+ return eSkip;
+}
+
+uint32_t filters::GetRow(Accessible* aAccessible) {
+ if (aAccessible->IsTableRow()) return eMatch | eSkipSubtree;
+
+ // Look for rows inside rowgroup or wrapping text containers.
+ a11y::role role = aAccessible->Role();
+ const nsRoleMapEntry* roleMapEntry = aAccessible->ARIARoleMap();
+ if (role == roles::GROUPING ||
+ (aAccessible->IsGenericHyperText() && !roleMapEntry)) {
+ return eSkip;
+ }
+
+ return eSkipSubtree;
+}
+
+uint32_t filters::GetCell(Accessible* aAccessible) {
+ return aAccessible->IsTableCell() ? eMatch : eSkipSubtree;
+}
diff --git a/accessible/base/Filters.h b/accessible/base/Filters.h
new file mode 100644
index 0000000000..dbc7a79595
--- /dev/null
+++ b/accessible/base/Filters.h
@@ -0,0 +1,46 @@
+/* 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_Filters_h__
+#define mozilla_a11y_Filters_h__
+
+#include <stdint.h>
+
+/**
+ * Predefined filters used for nsAccIterator and nsAccCollector.
+ */
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+namespace filters {
+
+enum EResult { eSkip = 0, eMatch = 1, eSkipSubtree = 2 };
+
+/**
+ * Return true if the traversed accessible complies with filter.
+ */
+typedef uint32_t (*FilterFuncPtr)(Accessible*);
+
+/**
+ * Matches selected/selectable accessibles in subtree.
+ */
+uint32_t GetSelected(Accessible* aAccessible);
+uint32_t GetSelectable(Accessible* aAccessible);
+
+/**
+ * Matches row accessibles in subtree.
+ */
+uint32_t GetRow(Accessible* aAccessible);
+
+/**
+ * Matches cell accessibles in children.
+ */
+uint32_t GetCell(Accessible* aAccessible);
+} // namespace filters
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/FocusManager.cpp b/accessible/base/FocusManager.cpp
new file mode 100644
index 0000000000..423b21dd9a
--- /dev/null
+++ b/accessible/base/FocusManager.cpp
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FocusManager.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "DocAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsEventShell.h"
+#include "Role.h"
+
+#include "nsFocusManager.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowserParent.h"
+
+namespace mozilla {
+namespace a11y {
+
+FocusManager::FocusManager() {}
+
+FocusManager::~FocusManager() {}
+
+Accessible* FocusManager::FocusedAccessible() const {
+ if (mActiveItem) return mActiveItem;
+
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ DocAccessible* doc =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode)
+ : nullptr;
+ }
+
+ return nullptr;
+}
+
+bool FocusManager::IsFocused(const Accessible* aAccessible) const {
+ if (mActiveItem) return mActiveItem == aAccessible;
+
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ // XXX: Before getting an accessible for node having a DOM focus make sure
+ // they belong to the same document because it can trigger unwanted document
+ // accessible creation for temporary about:blank document. Without this
+ // peculiarity we would end up with plain implementation based on
+ // FocusedAccessible() method call. Make sure this issue is fixed in
+ // bug 638465.
+ if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
+ DocAccessible* doc =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ return aAccessible ==
+ (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode)
+ : nullptr);
+ }
+ }
+ return false;
+}
+
+bool FocusManager::IsFocusWithin(const Accessible* aContainer) const {
+ Accessible* child = FocusedAccessible();
+ while (child) {
+ if (child == aContainer) return true;
+
+ child = child->Parent();
+ }
+ return false;
+}
+
+FocusManager::FocusDisposition FocusManager::IsInOrContainsFocus(
+ const Accessible* aAccessible) const {
+ Accessible* focus = FocusedAccessible();
+ if (!focus) return eNone;
+
+ // If focused.
+ if (focus == aAccessible) return eFocused;
+
+ // If contains the focus.
+ Accessible* child = focus->Parent();
+ while (child) {
+ if (child == aAccessible) return eContainsFocus;
+
+ child = child->Parent();
+ }
+
+ // If contained by focus.
+ child = aAccessible->Parent();
+ while (child) {
+ if (child == focus) return eContainedByFocus;
+
+ child = child->Parent();
+ }
+
+ return eNone;
+}
+
+bool FocusManager::WasLastFocused(const Accessible* aAccessible) const {
+ return mLastFocus == aAccessible;
+}
+
+void FocusManager::NotifyOfDOMFocus(nsISupports* aTarget) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
+#endif
+
+ mActiveItem = nullptr;
+
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
+ if (targetNode) {
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
+ if (document) {
+ // Set selection listener for focused element.
+ if (targetNode->IsElement())
+ SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
+
+ document->HandleNotification<FocusManager, nsINode>(
+ this, &FocusManager::ProcessDOMFocus, targetNode);
+ }
+ }
+}
+
+void FocusManager::NotifyOfDOMBlur(nsISupports* aTarget) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
+#endif
+
+ mActiveItem = nullptr;
+
+ // If DOM document stays focused then fire accessible focus event to process
+ // the case when no element within this DOM document will be focused.
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
+ if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
+ dom::Document* DOMDoc = targetNode->OwnerDoc();
+ DocAccessible* document = GetAccService()->GetDocAccessible(DOMDoc);
+ if (document) {
+ // Clear selection listener for previously focused element.
+ if (targetNode->IsElement())
+ SelectionMgr()->ClearControlSelectionListener();
+
+ document->HandleNotification<FocusManager, nsINode>(
+ this, &FocusManager::ProcessDOMFocus, DOMDoc);
+ }
+ }
+}
+
+void FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("active item changed", "Item", aItem);
+#endif
+
+ // Nothing changed, happens for XUL trees and HTML selects.
+ if (aItem && aItem == mActiveItem) return;
+
+ mActiveItem = nullptr;
+
+ if (aItem && aCheckIfActive) {
+ Accessible* widget = aItem->ContainerWidget();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus)) logging::ActiveWidget(widget);
+#endif
+ if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
+ return;
+ }
+ mActiveItem = aItem;
+
+ // If mActiveItem is null we may need to shift a11y focus back to a remote
+ // document. For example, when combobox popup is closed, then
+ // the focus should be moved back to the combobox.
+ if (!mActiveItem && XRE_IsParentProcess()) {
+ dom::BrowserParent* browser = dom::BrowserParent::GetFocused();
+ if (browser) {
+ a11y::DocAccessibleParent* dap = browser->GetTopLevelDocAccessible();
+ if (dap) {
+ Unused << dap->SendRestoreFocus();
+ }
+ }
+ }
+
+ // If active item is changed then fire accessible focus event on it, otherwise
+ // if there's no an active item then fire focus event to accessible having
+ // DOM focus.
+ Accessible* target = FocusedAccessible();
+ if (target) {
+ DispatchFocusEvent(target->Document(), target);
+ }
+}
+
+void FocusManager::ForceFocusEvent() {
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ if (document) {
+ document->HandleNotification<FocusManager, nsINode>(
+ this, &FocusManager::ProcessDOMFocus, focusedNode);
+ }
+ }
+}
+
+void FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
+ Accessible* aTarget) {
+ MOZ_ASSERT(aDocument, "No document for focused accessible!");
+ if (aDocument) {
+ RefPtr<AccEvent> event =
+ new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget, eAutoDetect,
+ AccEvent::eCoalesceOfSameType);
+ aDocument->FireDelayedEvent(event);
+ mLastFocus = aTarget;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus)) logging::FocusDispatched(aTarget);
+#endif
+ }
+}
+
+void FocusManager::ProcessDOMFocus(nsINode* aTarget) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
+#endif
+
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
+ if (!document) return;
+
+ Accessible* target =
+ document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
+ if (target) {
+ // Check if still focused. Otherwise we can end up with storing the active
+ // item for control that isn't focused anymore.
+ nsINode* focusedNode = FocusedDOMNode();
+ if (!focusedNode) return;
+
+ Accessible* DOMFocus =
+ document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
+ if (target != DOMFocus) return;
+
+ Accessible* activeItem = target->CurrentItem();
+ if (activeItem) {
+ mActiveItem = activeItem;
+ target = activeItem;
+ }
+
+ DispatchFocusEvent(document, target);
+ }
+}
+
+void FocusManager::ProcessFocusEvent(AccEvent* aEvent) {
+ MOZ_ASSERT(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
+ "Focus event is expected!");
+
+ // Emit focus event if event target is the active item. Otherwise then check
+ // if it's still focused and then update active item and emit focus event.
+ Accessible* target = aEvent->GetAccessible();
+ MOZ_ASSERT(!target->IsDefunct());
+ if (target != mActiveItem) {
+ // Check if still focused. Otherwise we can end up with storing the active
+ // item for control that isn't focused anymore.
+ DocAccessible* document = aEvent->Document();
+ nsINode* focusedNode = FocusedDOMNode();
+ if (!focusedNode) return;
+
+ Accessible* DOMFocus =
+ document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
+ if (target != DOMFocus) return;
+
+ Accessible* activeItem = target->CurrentItem();
+ if (activeItem) {
+ mActiveItem = activeItem;
+ target = activeItem;
+ MOZ_ASSERT(!target->IsDefunct());
+ }
+ }
+
+ // Fire menu start/end events for ARIA menus.
+ if (target->IsARIARole(nsGkAtoms::menuitem)) {
+ // The focus was moved into menu.
+ Accessible* ARIAMenubar = nullptr;
+ for (Accessible* parent = target->Parent(); parent;
+ parent = parent->Parent()) {
+ if (parent->IsARIARole(nsGkAtoms::menubar)) {
+ ARIAMenubar = parent;
+ break;
+ }
+
+ // Go up in the parent chain of the menu hierarchy.
+ if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
+ !parent->IsARIARole(nsGkAtoms::menu)) {
+ break;
+ }
+ }
+
+ if (ARIAMenubar != mActiveARIAMenubar) {
+ // Leaving ARIA menu. Fire menu_end event on current menubar.
+ if (mActiveARIAMenubar) {
+ RefPtr<AccEvent> menuEndEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
+ aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuEndEvent);
+ }
+
+ mActiveARIAMenubar = ARIAMenubar;
+
+ // Entering ARIA menu. Fire menu_start event.
+ if (mActiveARIAMenubar) {
+ RefPtr<AccEvent> menuStartEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
+ mActiveARIAMenubar, aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuStartEvent);
+ }
+ }
+ } else if (mActiveARIAMenubar) {
+ // Focus left a menu. Fire menu_end event.
+ RefPtr<AccEvent> menuEndEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
+ aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuEndEvent);
+
+ mActiveARIAMenubar = nullptr;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("fire focus event", "Target", target);
+#endif
+
+ // Reset cached caret value. The cache will be updated upon processing the
+ // next caret move event. This ensures that we will return the correct caret
+ // offset before the caret move event is handled.
+ SelectionMgr()->ResetCaretOffset();
+
+ RefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS,
+ target, aEvent->FromUserInput());
+ nsEventShell::FireEvent(focusEvent);
+
+ if (NS_WARN_IF(target->IsDefunct())) {
+ // target died during nsEventShell::FireEvent.
+ return;
+ }
+
+ // Fire scrolling_start event when the document receives the focus if it has
+ // an anchor jump. If an accessible within the document receive the focus
+ // then null out the anchor jump because it no longer applies.
+ DocAccessible* targetDocument = target->Document();
+ MOZ_ASSERT(targetDocument);
+ Accessible* anchorJump = targetDocument->AnchorJump();
+ if (anchorJump) {
+ if (target == targetDocument) {
+ // XXX: bug 625699, note in some cases the node could go away before we
+ // we receive focus event, for example if the node is removed from DOM.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
+ anchorJump, aEvent->FromUserInput());
+ }
+ targetDocument->SetAnchorJump(nullptr);
+ }
+}
+
+nsINode* FocusManager::FocusedDOMNode() const {
+ nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
+ nsIContent* focusedElm = DOMFocusManager->GetFocusedElement();
+ nsIFrame* focusedFrame = focusedElm ? focusedElm->GetPrimaryFrame() : nullptr;
+ // DOM elements retain their focused state when they get styled as display:
+ // none/content or visibility: hidden. We should treat those cases as if those
+ // elements were removed, and focus on doc.
+ if (focusedFrame && focusedFrame->StyleVisibility()->IsVisible()) {
+ // Print preview documents don't get DocAccessibles, but we still want a11y
+ // focus to go somewhere useful. Therefore, we allow a11y focus to land on
+ // the OuterDocAccessible in this case.
+ // Note that this code only handles remote print preview documents.
+ if (EventStateManager::IsTopLevelRemoteTarget(focusedElm) &&
+ focusedElm->AsElement()->HasAttribute(u"printpreview"_ns)) {
+ return focusedElm;
+ }
+ // No focus on remote target elements like xul:browser having DOM focus and
+ // residing in chrome process because it means an element in content process
+ // keeps the focus. Similarly, suppress focus on OOP iframes because an
+ // element in another content process should now have the focus.
+ if (EventStateManager::IsRemoteTarget(focusedElm)) {
+ return nullptr;
+ }
+ return focusedElm;
+ }
+
+ // Otherwise the focus can be on DOM document.
+ dom::BrowsingContext* context = DOMFocusManager->GetFocusedBrowsingContext();
+ if (context) {
+ // GetDocShell will return null if the document isn't in our process.
+ nsIDocShell* shell = context->GetDocShell();
+ if (shell) {
+ return shell->GetDocument();
+ }
+ }
+
+ // Focus isn't in this process.
+ return nullptr;
+}
+
+dom::Document* FocusManager::FocusedDOMDocument() const {
+ nsINode* focusedNode = FocusedDOMNode();
+ return focusedNode ? focusedNode->OwnerDoc() : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/FocusManager.h b/accessible/base/FocusManager.h
new file mode 100644
index 0000000000..475229376c
--- /dev/null
+++ b/accessible/base/FocusManager.h
@@ -0,0 +1,146 @@
+/* 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_FocusManager_h_
+#define mozilla_a11y_FocusManager_h_
+
+#include "mozilla/RefPtr.h"
+
+class nsINode;
+class nsISupports;
+
+namespace mozilla {
+namespace dom {
+class Document;
+}
+
+namespace a11y {
+
+class AccEvent;
+class Accessible;
+class DocAccessible;
+
+/**
+ * Manage the accessible focus. Used to fire and process accessible events.
+ */
+class FocusManager {
+ public:
+ virtual ~FocusManager();
+
+ /**
+ * Return a focused accessible.
+ */
+ Accessible* FocusedAccessible() const;
+
+ /**
+ * Return true if given accessible is focused.
+ */
+ bool IsFocused(const Accessible* aAccessible) const;
+
+ /**
+ * Return true if the given accessible is an active item, i.e. an item that
+ * is current within the active widget.
+ */
+ inline bool IsActiveItem(const Accessible* aAccessible) {
+ return aAccessible == mActiveItem;
+ }
+
+ /**
+ * Return DOM node having DOM focus.
+ */
+ nsINode* FocusedDOMNode() const;
+
+ /**
+ * Return true if given DOM node has DOM focus.
+ */
+ inline bool HasDOMFocus(const nsINode* aNode) const {
+ return aNode == FocusedDOMNode();
+ }
+
+ /**
+ * Return true if focused accessible is within the given container.
+ */
+ bool IsFocusWithin(const Accessible* aContainer) const;
+
+ /**
+ * Return whether the given accessible is focused or contains the focus or
+ * contained by focused accessible.
+ */
+ enum FocusDisposition { eNone, eFocused, eContainsFocus, eContainedByFocus };
+ FocusDisposition IsInOrContainsFocus(const Accessible* aAccessible) const;
+
+ /**
+ * Return true if the given accessible was the last accessible focused.
+ * This is useful to detect the case where the last focused accessible was
+ * removed before something else was focused. This can happen in one of two
+ * ways:
+ * 1. The DOM focus was removed. DOM doesn't fire a blur event when this
+ * happens; see bug 559561.
+ * 2. The accessibility focus was an active item (e.g. aria-activedescendant)
+ * and that item was removed.
+ */
+ bool WasLastFocused(const Accessible* aAccessible) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Focus notifications and processing (don't use until you know what you do).
+
+ /**
+ * Called when DOM focus event is fired.
+ */
+ void NotifyOfDOMFocus(nsISupports* aTarget);
+
+ /**
+ * Called when DOM blur event is fired.
+ */
+ void NotifyOfDOMBlur(nsISupports* aTarget);
+
+ /**
+ * Called when active item is changed. Note: must be called when accessible
+ * tree is up to date.
+ */
+ void ActiveItemChanged(Accessible* aItem, bool aCheckIfActive = true);
+
+ /**
+ * Dispatch delayed focus event for the current focus accessible.
+ */
+ void ForceFocusEvent();
+
+ /**
+ * Dispatch delayed focus event for the given target.
+ */
+ void DispatchFocusEvent(DocAccessible* aDocument, Accessible* aTarget);
+
+ /**
+ * Process DOM focus notification.
+ */
+ void ProcessDOMFocus(nsINode* aTarget);
+
+ /**
+ * Process the delayed accessible event.
+ * do.
+ */
+ void ProcessFocusEvent(AccEvent* aEvent);
+
+ protected:
+ FocusManager();
+
+ private:
+ FocusManager(const FocusManager&);
+ FocusManager& operator=(const FocusManager&);
+
+ /**
+ * Return DOM document having DOM focus.
+ */
+ dom::Document* FocusedDOMDocument() const;
+
+ private:
+ RefPtr<Accessible> mActiveItem;
+ RefPtr<Accessible> mLastFocus;
+ RefPtr<Accessible> mActiveARIAMenubar;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/IDSet.h b/accessible/base/IDSet.h
new file mode 100644
index 0000000000..a149bf95a3
--- /dev/null
+++ b/accessible/base/IDSet.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+/**
+ * A class to generate unique IDs in the range [ - 2^31, 0 )
+ */
+
+#ifndef MOZILLA_A11Y_IDSet_h_
+#define MOZILLA_A11Y_IDSet_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/SplayTree.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * On windows an accessible's id must be a negative 32 bit integer. It is
+ * important to support recycling arbitrary IDs because accessibles can be
+ * created and destroyed at any time in the life of a page. IDSet provides 2
+ * operations: generate an ID in the range (0, mMaxId], and release an ID so
+ * it can be allocated again. Allocated ID are tracked by a sparse bitmap
+ * implemented with a splay tree. Nodes in the tree are keyed by the upper N
+ * bits of the ID, and the node contains a bitmap tracking the allocation of
+ * 2^(ceil(log2(mMaxId)) - N) IDs.
+ *
+ * Note that negation is handled by MsaaIdGenerator as it performs additional
+ * decoration on the ID generated by IDSet.
+ * @see mozilla::a11y::MsaaIdGenerator
+ */
+class IDSet {
+ public:
+ constexpr explicit IDSet(const uint32_t aMaxIdBits)
+ : mBitSet(),
+ mIdx(0),
+ mMaxId((1UL << aMaxIdBits) - 1UL),
+ mMaxIdx(mMaxId / bitsPerElt) {}
+
+ /**
+ * Return a new unique id.
+ */
+ uint32_t GetID() {
+ uint32_t idx = mIdx;
+ while (true) {
+ BitSetElt* elt = mBitSet.findOrInsert(BitSetElt(idx));
+ if (elt->mBitvec[0] != UINT64_MAX) {
+ uint32_t i = CountTrailingZeroes64(~elt->mBitvec[0]);
+
+ elt->mBitvec[0] |= (1ull << i);
+ mIdx = idx;
+ return (elt->mIdx * bitsPerElt + i);
+ }
+
+ if (elt->mBitvec[1] != UINT64_MAX) {
+ uint32_t i = CountTrailingZeroes64(~elt->mBitvec[1]);
+
+ elt->mBitvec[1] |= (1ull << i);
+ mIdx = idx;
+ return (elt->mIdx * bitsPerElt + bitsPerWord + i);
+ }
+
+ idx++;
+ if (idx > mMaxIdx) {
+ idx = 0;
+ }
+
+ if (idx == mIdx) {
+ MOZ_CRASH("used up all the available ids");
+ }
+ }
+ }
+
+ /**
+ * Free a no longer required id so it may be allocated again.
+ */
+ void ReleaseID(uint32_t aID) {
+ MOZ_ASSERT(aID < mMaxId);
+
+ uint32_t idx = aID / bitsPerElt;
+ mIdx = idx;
+ BitSetElt* elt = mBitSet.find(BitSetElt(idx));
+ MOZ_ASSERT(elt);
+
+ uint32_t vecIdx = (aID % bitsPerElt) / bitsPerWord;
+ elt->mBitvec[vecIdx] &= ~(1ull << (aID % bitsPerWord));
+ if (elt->mBitvec[0] == 0 && elt->mBitvec[1] == 0) {
+ delete mBitSet.remove(*elt);
+ }
+ }
+
+ private:
+ static const unsigned int wordsPerElt = 2;
+ static const unsigned int bitsPerWord = 64;
+ static const unsigned int bitsPerElt = wordsPerElt * bitsPerWord;
+
+ struct BitSetElt : mozilla::SplayTreeNode<BitSetElt> {
+ explicit BitSetElt(uint32_t aIdx) : mIdx(aIdx) {
+ mBitvec[0] = mBitvec[1] = 0;
+ }
+
+ uint64_t mBitvec[wordsPerElt];
+ uint32_t mIdx;
+
+ static int compare(const BitSetElt& a, const BitSetElt& b) {
+ if (a.mIdx == b.mIdx) {
+ return 0;
+ }
+
+ if (a.mIdx < b.mIdx) {
+ return -1;
+ }
+ return 1;
+ }
+ };
+
+ SplayTree<BitSetElt, BitSetElt> mBitSet;
+ uint32_t mIdx;
+ const uint32_t mMaxId;
+ const uint32_t mMaxIdx;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/Logging.cpp b/accessible/base/Logging.cpp
new file mode 100644
index 0000000000..c6caaa3827
--- /dev/null
+++ b/accessible/base/Logging.cpp
@@ -0,0 +1,925 @@
+/* -*- 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 "Logging.h"
+
+#include "Accessible-inl.h"
+#include "AccEvent.h"
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+#include "OuterDocAccessible.h"
+
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsTraceRefcnt.h"
+#include "nsIWebProgress.h"
+#include "prenv.h"
+#include "nsIDocShellTreeItem.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/Selection.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+using mozilla::dom::BorrowedAttrInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+// Logging helpers
+
+static uint32_t sModules = 0;
+
+struct ModuleRep {
+ const char* mStr;
+ logging::EModules mModule;
+};
+
+static ModuleRep sModuleMap[] = {{"docload", logging::eDocLoad},
+ {"doccreate", logging::eDocCreate},
+ {"docdestroy", logging::eDocDestroy},
+ {"doclifecycle", logging::eDocLifeCycle},
+
+ {"events", logging::eEvents},
+ {"eventTree", logging::eEventTree},
+ {"platforms", logging::ePlatforms},
+ {"text", logging::eText},
+ {"tree", logging::eTree},
+
+ {"DOMEvents", logging::eDOMEvents},
+ {"focus", logging::eFocus},
+ {"selection", logging::eSelection},
+ {"notifications", logging::eNotifications},
+
+ {"stack", logging::eStack},
+ {"verbose", logging::eVerbose}};
+
+static void EnableLogging(const char* aModulesStr) {
+ sModules = 0;
+ if (!aModulesStr) return;
+
+ const char* token = aModulesStr;
+ while (*token != '\0') {
+ size_t tokenLen = strcspn(token, ",");
+ for (unsigned int idx = 0; idx < ArrayLength(sModuleMap); idx++) {
+ if (strncmp(token, sModuleMap[idx].mStr, tokenLen) == 0) {
+#if !defined(MOZ_PROFILING) && (!defined(DEBUG) || defined(MOZ_OPTIMIZE))
+ // Stack tracing on profiling enabled or debug not optimized builds.
+ if (strncmp(token, "stack", tokenLen) == 0) break;
+#endif
+ sModules |= sModuleMap[idx].mModule;
+ printf("\n\nmodule enabled: %s\n", sModuleMap[idx].mStr);
+ break;
+ }
+ }
+ token += tokenLen;
+
+ if (*token == ',') token++; // skip ',' char
+ }
+}
+
+static void LogDocURI(dom::Document* aDocumentNode) {
+ printf("uri: %s", aDocumentNode->GetDocumentURI()->GetSpecOrDefault().get());
+}
+
+static void LogDocShellState(dom::Document* aDocumentNode) {
+ printf("docshell busy: ");
+
+ nsAutoCString docShellBusy;
+ nsCOMPtr<nsIDocShell> docShell = aDocumentNode->GetDocShell();
+ nsIDocShell::BusyFlags busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
+ docShell->GetBusyFlags(&busyFlags);
+ if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) {
+ printf("'none'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY) {
+ printf("'busy'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_BEFORE_PAGE_LOAD) {
+ printf(", 'before page load'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) {
+ printf(", 'page loading'");
+ }
+}
+
+static void LogDocType(dom::Document* aDocumentNode) {
+ if (aDocumentNode->IsActive()) {
+ bool isContent = nsCoreUtils::IsContentDocument(aDocumentNode);
+ printf("%s document", (isContent ? "content" : "chrome"));
+ } else {
+ printf("document type: [failed]");
+ }
+}
+
+static void LogDocShellTree(dom::Document* aDocumentNode) {
+ if (aDocumentNode->IsActive()) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetInProcessParent(getter_AddRefs(parentTreeItem));
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
+ printf(
+ "in-process docshell hierarchy, parent: %p, root: %p, "
+ "is top level: %s;",
+ static_cast<void*>(parentTreeItem), static_cast<void*>(rootTreeItem),
+ (nsCoreUtils::IsTopLevelContentDocInProcess(aDocumentNode) ? "yes"
+ : "no"));
+ }
+}
+
+static void LogDocState(dom::Document* aDocumentNode) {
+ const char* docState = nullptr;
+ dom::Document::ReadyState docStateFlag = aDocumentNode->GetReadyStateEnum();
+ switch (docStateFlag) {
+ case dom::Document::READYSTATE_UNINITIALIZED:
+ docState = "uninitialized";
+ break;
+ case dom::Document::READYSTATE_LOADING:
+ docState = "loading";
+ break;
+ case dom::Document::READYSTATE_INTERACTIVE:
+ docState = "interactive";
+ break;
+ case dom::Document::READYSTATE_COMPLETE:
+ docState = "complete";
+ break;
+ }
+
+ printf("doc state: %s", docState);
+ printf(", %sinitial", aDocumentNode->IsInitialDocument() ? "" : "not ");
+ printf(", %sshowing", aDocumentNode->IsShowing() ? "" : "not ");
+ printf(", %svisible", aDocumentNode->IsVisible() ? "" : "not ");
+ printf(", %svisible considering ancestors",
+ aDocumentNode->IsVisibleConsideringAncestors() ? "" : "not ");
+ printf(", %sactive", aDocumentNode->IsActive() ? "" : "not ");
+ printf(", %sresource", aDocumentNode->IsResourceDoc() ? "" : "not ");
+
+ dom::Element* rootEl = aDocumentNode->GetBodyElement();
+ if (!rootEl) {
+ rootEl = aDocumentNode->GetRootElement();
+ }
+ printf(", has %srole content", rootEl ? "" : "no ");
+}
+
+static void LogPresShell(dom::Document* aDocumentNode) {
+ PresShell* presShell = aDocumentNode->GetPresShell();
+ printf("presshell: %p", static_cast<void*>(presShell));
+
+ nsIScrollableFrame* sf = nullptr;
+ if (presShell) {
+ printf(", is %s destroying", (presShell->IsDestroying() ? "" : "not"));
+ sf = presShell->GetRootScrollFrameAsScrollable();
+ }
+ printf(", root scroll frame: %p", static_cast<void*>(sf));
+}
+
+static void LogDocLoadGroup(dom::Document* aDocumentNode) {
+ nsCOMPtr<nsILoadGroup> loadGroup = aDocumentNode->GetDocumentLoadGroup();
+ printf("load group: %p", static_cast<void*>(loadGroup));
+}
+
+static void LogDocParent(dom::Document* aDocumentNode) {
+ dom::Document* parentDoc = aDocumentNode->GetInProcessParentDocument();
+ printf("parent DOM document: %p", static_cast<void*>(parentDoc));
+ if (parentDoc) {
+ printf(", parent acc document: %p",
+ static_cast<void*>(GetExistingDocAccessible(parentDoc)));
+ printf("\n parent ");
+ LogDocURI(parentDoc);
+ printf("\n");
+ }
+}
+
+static void LogDocInfo(dom::Document* aDocumentNode, DocAccessible* aDocument) {
+ printf(" DOM document: %p, acc document: %p\n ",
+ static_cast<void*>(aDocumentNode), static_cast<void*>(aDocument));
+
+ // log document info
+ if (aDocumentNode) {
+ LogDocURI(aDocumentNode);
+ printf("\n ");
+ LogDocShellState(aDocumentNode);
+ printf("; ");
+ LogDocType(aDocumentNode);
+ printf("\n ");
+ LogDocShellTree(aDocumentNode);
+ printf("\n ");
+ LogDocState(aDocumentNode);
+ printf("\n ");
+ LogPresShell(aDocumentNode);
+ printf("\n ");
+ LogDocLoadGroup(aDocumentNode);
+ printf(", ");
+ LogDocParent(aDocumentNode);
+ printf("\n");
+ }
+}
+
+static void LogShellLoadType(nsIDocShell* aDocShell) {
+ printf("load type: ");
+
+ uint32_t loadType = 0;
+ aDocShell->GetLoadType(&loadType);
+ switch (loadType) {
+ case LOAD_NORMAL:
+ printf("normal; ");
+ break;
+ case LOAD_NORMAL_REPLACE:
+ printf("normal replace; ");
+ break;
+ case LOAD_NORMAL_EXTERNAL:
+ printf("normal external; ");
+ break;
+ case LOAD_HISTORY:
+ printf("history; ");
+ break;
+ case LOAD_NORMAL_BYPASS_CACHE:
+ printf("normal bypass cache; ");
+ break;
+ case LOAD_NORMAL_BYPASS_PROXY:
+ printf("normal bypass proxy; ");
+ break;
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ printf("normal bypass proxy and cache; ");
+ break;
+ case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+ printf("normal allow mixed content; ");
+ break;
+ case LOAD_RELOAD_NORMAL:
+ printf("reload normal; ");
+ break;
+ case LOAD_RELOAD_BYPASS_CACHE:
+ printf("reload bypass cache; ");
+ break;
+ case LOAD_RELOAD_BYPASS_PROXY:
+ printf("reload bypass proxy; ");
+ break;
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ printf("reload bypass proxy and cache; ");
+ break;
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ printf("reload allow mixed content; ");
+ break;
+ case LOAD_LINK:
+ printf("link; ");
+ break;
+ case LOAD_REFRESH:
+ printf("refresh; ");
+ break;
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ printf("reload charset change; ");
+ break;
+ case LOAD_BYPASS_HISTORY:
+ printf("bypass history; ");
+ break;
+ case LOAD_STOP_CONTENT:
+ printf("stop content; ");
+ break;
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ printf("stop content and replace; ");
+ break;
+ case LOAD_PUSHSTATE:
+ printf("load pushstate; ");
+ break;
+ case LOAD_REPLACE_BYPASS_CACHE:
+ printf("replace bypass cache; ");
+ break;
+ case LOAD_ERROR_PAGE:
+ printf("error page;");
+ break;
+ default:
+ printf("unknown");
+ }
+}
+
+static void LogRequest(nsIRequest* aRequest) {
+ if (aRequest) {
+ nsAutoCString name;
+ aRequest->GetName(name);
+ printf(" request spec: %s\n", name.get());
+ uint32_t loadFlags = 0;
+ aRequest->GetLoadFlags(&loadFlags);
+ printf(" request load flags: %x; ", loadFlags);
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) printf("document uri; ");
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
+ printf("retargeted document uri; ");
+ if (loadFlags & nsIChannel::LOAD_REPLACE) printf("replace; ");
+ if (loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)
+ printf("initial document uri; ");
+ if (loadFlags & nsIChannel::LOAD_TARGETED) printf("targeted; ");
+ if (loadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS)
+ printf("call content sniffers; ");
+ if (loadFlags & nsIChannel::LOAD_BYPASS_URL_CLASSIFIER) {
+ printf("bypass classify uri; ");
+ }
+ } else {
+ printf(" no request");
+ }
+}
+
+static void LogDocAccState(DocAccessible* aDocument) {
+ printf("document acc state: ");
+ if (aDocument->HasLoadState(DocAccessible::eCompletelyLoaded))
+ printf("completely loaded;");
+ else if (aDocument->HasLoadState(DocAccessible::eReady))
+ printf("ready;");
+ else if (aDocument->HasLoadState(DocAccessible::eDOMLoaded))
+ printf("DOM loaded;");
+ else if (aDocument->HasLoadState(DocAccessible::eTreeConstructed))
+ printf("tree constructed;");
+}
+
+static void GetDocLoadEventType(AccEvent* aEvent, nsACString& aEventType) {
+ uint32_t type = aEvent->GetEventType();
+ if (type == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED) {
+ aEventType.AssignLiteral("load stopped");
+ } else if (type == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE) {
+ aEventType.AssignLiteral("load complete");
+ } else if (type == nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD) {
+ aEventType.AssignLiteral("reload");
+ } else if (type == nsIAccessibleEvent::EVENT_STATE_CHANGE) {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ if (event->GetState() == states::BUSY) {
+ aEventType.AssignLiteral("busy ");
+ if (event->IsStateEnabled())
+ aEventType.AppendLiteral("true");
+ else
+ aEventType.AppendLiteral("false");
+ }
+ }
+}
+
+static void DescribeNode(nsINode* aNode, nsAString& aOutDescription) {
+ if (!aNode) {
+ aOutDescription.AppendLiteral("null");
+ return;
+ }
+
+ aOutDescription.AppendPrintf("0x%p, ", (void*)aNode);
+ aOutDescription.Append(aNode->NodeInfo()->QualifiedName());
+
+ if (!aNode->IsElement()) {
+ return;
+ }
+
+ dom::Element* elm = aNode->AsElement();
+
+ nsAtom* idAtom = elm->GetID();
+ if (idAtom) {
+ nsAutoCString id;
+ idAtom->ToUTF8String(id);
+ aOutDescription.AppendPrintf("@id=\"%s\" ", id.get());
+ } else {
+ aOutDescription.Append(' ');
+ }
+
+ uint32_t attrCount = elm->GetAttrCount();
+ if (!attrCount || (idAtom && attrCount == 1)) {
+ return;
+ }
+
+ aOutDescription.AppendLiteral("[ ");
+
+ for (uint32_t index = 0; index < attrCount; index++) {
+ BorrowedAttrInfo info = elm->GetAttrInfoAt(index);
+
+ // Skip redundant display of id attribute.
+ if (info.mName->Equals(nsGkAtoms::id)) {
+ continue;
+ }
+
+ // name
+ nsAutoString name;
+ info.mName->GetQualifiedName(name);
+ aOutDescription.Append(name);
+
+ aOutDescription.AppendLiteral("=\"");
+
+ // value
+ nsAutoString value;
+ info.mValue->ToString(value);
+ for (uint32_t i = value.Length(); i > 0; --i) {
+ if (value[i - 1] == char16_t('"')) value.Insert(char16_t('\\'), i - 1);
+ }
+ aOutDescription.Append(value);
+ aOutDescription.AppendLiteral("\" ");
+ }
+
+ aOutDescription.Append(']');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace logging:: document life cycle logging methods
+
+static const char* sDocLoadTitle = "DOCLOAD";
+static const char* sDocCreateTitle = "DOCCREATE";
+static const char* sDocDestroyTitle = "DOCDESTROY";
+static const char* sDocEventTitle = "DOCEVENT";
+static const char* sFocusTitle = "FOCUS";
+
+void logging::DocLoad(const char* aMsg, nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags) {
+ MsgBegin(sDocLoadTitle, "%s", aMsg);
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ nsPIDOMWindowOuter* window = nsPIDOMWindowOuter::From(DOMWindow);
+ if (!window) {
+ MsgEnd();
+ return;
+ }
+
+ nsCOMPtr<dom::Document> documentNode = window->GetDoc();
+ if (!documentNode) {
+ MsgEnd();
+ return;
+ }
+
+ DocAccessible* document = GetExistingDocAccessible(documentNode);
+
+ LogDocInfo(documentNode, document);
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ printf("\n ");
+ LogShellLoadType(docShell);
+ printf("\n");
+ LogRequest(aRequest);
+ printf("\n");
+ printf(" state flags: %x", aStateFlags);
+ bool isDocLoading;
+ aWebProgress->GetIsLoadingDocument(&isDocLoading);
+ printf(", document is %sloading\n", (isDocLoading ? "" : "not "));
+
+ MsgEnd();
+}
+
+void logging::DocLoad(const char* aMsg, dom::Document* aDocumentNode) {
+ MsgBegin(sDocLoadTitle, "%s", aMsg);
+
+ DocAccessible* document = GetExistingDocAccessible(aDocumentNode);
+ LogDocInfo(aDocumentNode, document);
+
+ MsgEnd();
+}
+
+void logging::DocCompleteLoad(DocAccessible* aDocument,
+ bool aIsLoadEventTarget) {
+ MsgBegin(sDocLoadTitle, "document loaded *completely*");
+
+ printf(" DOM document: %p, acc document: %p\n",
+ static_cast<void*>(aDocument->DocumentNode()),
+ static_cast<void*>(aDocument));
+
+ printf(" ");
+ LogDocURI(aDocument->DocumentNode());
+ printf("\n");
+
+ printf(" ");
+ LogDocAccState(aDocument);
+ printf("\n");
+
+ printf(" document is load event target: %s\n",
+ (aIsLoadEventTarget ? "true" : "false"));
+
+ MsgEnd();
+}
+
+void logging::DocLoadEventFired(AccEvent* aEvent) {
+ nsAutoCString strEventType;
+ GetDocLoadEventType(aEvent, strEventType);
+ if (!strEventType.IsEmpty()) printf(" fire: %s\n", strEventType.get());
+}
+
+void logging::DocLoadEventHandled(AccEvent* aEvent) {
+ nsAutoCString strEventType;
+ GetDocLoadEventType(aEvent, strEventType);
+ if (strEventType.IsEmpty()) return;
+
+ MsgBegin(sDocEventTitle, "handled '%s' event", strEventType.get());
+
+ DocAccessible* document = aEvent->GetAccessible()->AsDoc();
+ if (document) LogDocInfo(document->DocumentNode(), document);
+
+ MsgEnd();
+}
+
+void logging::DocCreate(const char* aMsg, dom::Document* aDocumentNode,
+ DocAccessible* aDocument) {
+ DocAccessible* document =
+ aDocument ? aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocCreateTitle, "%s", aMsg);
+ LogDocInfo(aDocumentNode, document);
+ MsgEnd();
+}
+
+void logging::DocDestroy(const char* aMsg, dom::Document* aDocumentNode,
+ DocAccessible* aDocument) {
+ DocAccessible* document =
+ aDocument ? aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocDestroyTitle, "%s", aMsg);
+ LogDocInfo(aDocumentNode, document);
+ MsgEnd();
+}
+
+void logging::OuterDocDestroy(OuterDocAccessible* aOuterDoc) {
+ MsgBegin(sDocDestroyTitle, "outerdoc shutdown");
+ logging::Address("outerdoc", aOuterDoc);
+ MsgEnd();
+}
+
+void logging::FocusNotificationTarget(const char* aMsg,
+ const char* aTargetDescr,
+ Accessible* aTarget) {
+ MsgBegin(sFocusTitle, "%s", aMsg);
+ AccessibleNNode(aTargetDescr, aTarget);
+ MsgEnd();
+}
+
+void logging::FocusNotificationTarget(const char* aMsg,
+ const char* aTargetDescr,
+ nsINode* aTargetNode) {
+ MsgBegin(sFocusTitle, "%s", aMsg);
+ Node(aTargetDescr, aTargetNode);
+ MsgEnd();
+}
+
+void logging::FocusNotificationTarget(const char* aMsg,
+ const char* aTargetDescr,
+ nsISupports* aTargetThing) {
+ MsgBegin(sFocusTitle, "%s", aMsg);
+
+ if (aTargetThing) {
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTargetThing));
+ if (targetNode)
+ AccessibleNNode(aTargetDescr, targetNode);
+ else
+ printf(" %s: %p, window\n", aTargetDescr,
+ static_cast<void*>(aTargetThing));
+ }
+
+ MsgEnd();
+}
+
+void logging::ActiveItemChangeCausedBy(const char* aCause,
+ Accessible* aTarget) {
+ SubMsgBegin();
+ printf(" Caused by: %s\n", aCause);
+ AccessibleNNode("Item", aTarget);
+ SubMsgEnd();
+}
+
+void logging::ActiveWidget(Accessible* aWidget) {
+ SubMsgBegin();
+
+ AccessibleNNode("Widget", aWidget);
+ printf(" Widget is active: %s, has operable items: %s\n",
+ (aWidget && aWidget->IsActiveWidget() ? "true" : "false"),
+ (aWidget && aWidget->AreItemsOperable() ? "true" : "false"));
+
+ SubMsgEnd();
+}
+
+void logging::FocusDispatched(Accessible* aTarget) {
+ SubMsgBegin();
+ AccessibleNNode("A11y target", aTarget);
+ SubMsgEnd();
+}
+
+void logging::SelChange(dom::Selection* aSelection, DocAccessible* aDocument,
+ int16_t aReason) {
+ SelectionType type = aSelection->GetType();
+
+ const char* strType = 0;
+ if (type == SelectionType::eNormal)
+ strType = "normal";
+ else if (type == SelectionType::eSpellCheck)
+ strType = "spellcheck";
+ else
+ strType = "unknown";
+
+ bool isIgnored = !aDocument || !aDocument->IsContentLoaded();
+ printf(
+ "\nSelection changed, selection type: %s, notification %s, reason: %d\n",
+ strType, (isIgnored ? "ignored" : "pending"), aReason);
+
+ Stack();
+}
+
+void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...) {
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ va_list vl;
+ va_start(vl, aExtraFlags);
+ const char* descr = va_arg(vl, const char*);
+ if (descr) {
+ Accessible* acc = va_arg(vl, Accessible*);
+ MsgBegin("TREE", "%s; doc: %p", aMsg, acc ? acc->Document() : nullptr);
+ AccessibleInfo(descr, acc);
+ while ((descr = va_arg(vl, const char*))) {
+ AccessibleInfo(descr, va_arg(vl, Accessible*));
+ }
+ } else {
+ MsgBegin("TREE", "%s", aMsg);
+ }
+ va_end(vl);
+ MsgEnd();
+
+ if (aExtraFlags & eStack) {
+ Stack();
+ }
+ }
+}
+
+void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags,
+ const char* aMsg1, Accessible* aAcc, const char* aMsg2,
+ nsINode* aNode) {
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ MsgBegin("TREE", "%s; doc: %p", aMsg, aAcc ? aAcc->Document() : nullptr);
+ AccessibleInfo(aMsg1, aAcc);
+ Accessible* acc = aAcc ? aAcc->Document()->GetAccessible(aNode) : nullptr;
+ if (acc) {
+ AccessibleInfo(aMsg2, acc);
+ } else {
+ Node(aMsg2, aNode);
+ }
+ MsgEnd();
+ }
+}
+
+void logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags,
+ Accessible* aParent) {
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ MsgBegin("TREE", "%s; doc: %p", aMsg, aParent->Document());
+ AccessibleInfo("container", aParent);
+ for (uint32_t idx = 0; idx < aParent->ChildCount(); idx++) {
+ AccessibleInfo("child", aParent->GetChildAt(idx));
+ }
+ MsgEnd();
+ }
+}
+
+void logging::Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
+ GetTreePrefix aPrefixFunc, void* aGetTreePrefixData) {
+ logging::MsgBegin(aTitle, "%s", aMsgText);
+
+ nsAutoString level;
+ Accessible* root = aRoot;
+ do {
+ const char* prefix =
+ aPrefixFunc ? aPrefixFunc(aGetTreePrefixData, root) : "";
+ printf("%s", NS_ConvertUTF16toUTF8(level).get());
+ logging::AccessibleInfo(prefix, root);
+ if (root->FirstChild() && !root->FirstChild()->IsDoc()) {
+ level.AppendLiteral(u" ");
+ root = root->FirstChild();
+ continue;
+ }
+ int32_t idxInParent = root != aRoot && root->mParent
+ ? root->mParent->mChildren.IndexOf(root)
+ : -1;
+ if (idxInParent != -1 &&
+ idxInParent <
+ static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+ root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+ continue;
+ }
+ while (root != aRoot && (root = root->Parent())) {
+ level.Cut(0, 2);
+ int32_t idxInParent = !root->IsDoc() && root->mParent
+ ? root->mParent->mChildren.IndexOf(root)
+ : -1;
+ if (idxInParent != -1 &&
+ idxInParent <
+ static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+ root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+ break;
+ }
+ }
+ } while (root && root != aRoot);
+
+ logging::MsgEnd();
+}
+
+void logging::DOMTree(const char* aTitle, const char* aMsgText,
+ DocAccessible* aDocument) {
+ logging::MsgBegin(aTitle, "%s", aMsgText);
+ nsAutoString level;
+ nsINode* root = aDocument->DocumentNode();
+ do {
+ printf("%s", NS_ConvertUTF16toUTF8(level).get());
+ logging::Node("", root);
+ if (root->GetFirstChild()) {
+ level.AppendLiteral(u" ");
+ root = root->GetFirstChild();
+ continue;
+ }
+ if (root->GetNextSibling()) {
+ root = root->GetNextSibling();
+ continue;
+ }
+ while ((root = root->GetParentNode())) {
+ level.Cut(0, 2);
+ if (root->GetNextSibling()) {
+ root = root->GetNextSibling();
+ break;
+ }
+ }
+ } while (root);
+ logging::MsgEnd();
+}
+
+void logging::MsgBegin(const char* aTitle, const char* aMsgText, ...) {
+ printf("\nA11Y %s: ", aTitle);
+
+ va_list argptr;
+ va_start(argptr, aMsgText);
+ vprintf(aMsgText, argptr);
+ va_end(argptr);
+
+ PRIntervalTime time = PR_IntervalNow();
+ uint32_t mins = (PR_IntervalToSeconds(time) / 60) % 60;
+ uint32_t secs = PR_IntervalToSeconds(time) % 60;
+ uint32_t msecs = PR_IntervalToMilliseconds(time) % 1000;
+ printf("; %02u:%02u.%03u", mins, secs, msecs);
+
+ printf("\n {\n");
+}
+
+void logging::MsgEnd() { printf(" }\n"); }
+
+void logging::SubMsgBegin() { printf(" {\n"); }
+
+void logging::SubMsgEnd() { printf(" }\n"); }
+
+void logging::MsgEntry(const char* aEntryText, ...) {
+ printf(" ");
+
+ va_list argptr;
+ va_start(argptr, aEntryText);
+ vprintf(aEntryText, argptr);
+ va_end(argptr);
+
+ printf("\n");
+}
+
+void logging::Text(const char* aText) { printf(" %s\n", aText); }
+
+void logging::Address(const char* aDescr, Accessible* aAcc) {
+ if (!aAcc->IsDoc()) {
+ printf(" %s accessible: %p, node: %p\n", aDescr,
+ static_cast<void*>(aAcc), static_cast<void*>(aAcc->GetNode()));
+ }
+
+ DocAccessible* doc = aAcc->Document();
+ dom::Document* docNode = doc->DocumentNode();
+ printf(" document: %p, node: %p\n", static_cast<void*>(doc),
+ static_cast<void*>(docNode));
+
+ printf(" ");
+ LogDocURI(docNode);
+ printf("\n");
+}
+
+void logging::Node(const char* aDescr, nsINode* aNode) {
+ nsINode* parentNode = aNode ? aNode->GetParentNode() : nullptr;
+ int32_t idxInParent = parentNode ? parentNode->ComputeIndexOf(aNode) : -1;
+
+ nsAutoString nodeDesc;
+ DescribeNode(aNode, nodeDesc);
+ printf(" %s: %s, idx in parent %d\n", aDescr,
+ NS_ConvertUTF16toUTF8(nodeDesc).get(), idxInParent);
+}
+
+void logging::Document(DocAccessible* aDocument) {
+ printf(" Document: %p, document node: %p\n", static_cast<void*>(aDocument),
+ static_cast<void*>(aDocument->DocumentNode()));
+
+ printf(" Document ");
+ LogDocURI(aDocument->DocumentNode());
+ printf("\n");
+}
+
+void logging::AccessibleInfo(const char* aDescr, Accessible* aAccessible) {
+ printf(" %s: %p; ", aDescr, static_cast<void*>(aAccessible));
+ if (!aAccessible) {
+ printf("\n");
+ return;
+ }
+ if (aAccessible->IsDefunct()) {
+ printf("defunct\n");
+ return;
+ }
+ if (!aAccessible->Document() || aAccessible->Document()->IsDefunct()) {
+ printf("document is shutting down, no info\n");
+ return;
+ }
+
+ nsAutoString role;
+ GetAccService()->GetStringRole(aAccessible->Role(), role);
+ printf("role: %s", NS_ConvertUTF16toUTF8(role).get());
+
+ nsAutoString name;
+ aAccessible->Name(name);
+ if (!name.IsEmpty()) {
+ printf(", name: '%s'", NS_ConvertUTF16toUTF8(name).get());
+ }
+
+ printf(", idx: %d", aAccessible->IndexInParent());
+
+ nsAutoString nodeDesc;
+ DescribeNode(aAccessible->GetNode(), nodeDesc);
+ printf(", node: %s\n", NS_ConvertUTF16toUTF8(nodeDesc).get());
+}
+
+void logging::AccessibleNNode(const char* aDescr, Accessible* aAccessible) {
+ printf(" %s: %p; ", aDescr, static_cast<void*>(aAccessible));
+ if (!aAccessible) return;
+
+ nsAutoString role;
+ GetAccService()->GetStringRole(aAccessible->Role(), role);
+ nsAutoString name;
+ aAccessible->Name(name);
+
+ printf("role: %s, name: '%s';\n", NS_ConvertUTF16toUTF8(role).get(),
+ NS_ConvertUTF16toUTF8(name).get());
+
+ nsAutoCString nodeDescr(aDescr);
+ nodeDescr.AppendLiteral(" node");
+ Node(nodeDescr.get(), aAccessible->GetNode());
+
+ Document(aAccessible->Document());
+}
+
+void logging::AccessibleNNode(const char* aDescr, nsINode* aNode) {
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(aNode->OwnerDoc());
+
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aNode);
+ if (accessible) {
+ AccessibleNNode(aDescr, accessible);
+ return;
+ }
+ }
+
+ nsAutoCString nodeDescr("[not accessible] ");
+ nodeDescr.Append(aDescr);
+ Node(nodeDescr.get(), aNode);
+
+ if (document) {
+ Document(document);
+ return;
+ }
+
+ printf(" [contained by not accessible document]:\n");
+ LogDocInfo(aNode->OwnerDoc(), document);
+ printf("\n");
+}
+
+void logging::DOMEvent(const char* aDescr, nsINode* aOrigTarget,
+ const nsAString& aEventType) {
+ logging::MsgBegin("DOMEvents", "event '%s' %s",
+ NS_ConvertUTF16toUTF8(aEventType).get(), aDescr);
+ logging::AccessibleNNode("Target", aOrigTarget);
+ logging::MsgEnd();
+}
+
+void logging::Stack() {
+ if (IsEnabled(eStack)) {
+ printf(" stack: \n");
+ nsTraceRefcnt::WalkTheStack(stdout);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace logging:: initialization
+
+bool logging::IsEnabled(uint32_t aModules) { return sModules & aModules; }
+
+bool logging::IsEnabledAll(uint32_t aModules) {
+ return (sModules & aModules) == aModules;
+}
+
+bool logging::IsEnabled(const nsAString& aModuleStr) {
+ for (unsigned int idx = 0; idx < ArrayLength(sModuleMap); idx++) {
+ if (aModuleStr.EqualsASCII(sModuleMap[idx].mStr))
+ return sModules & sModuleMap[idx].mModule;
+ }
+
+ return false;
+}
+
+void logging::Enable(const nsCString& aModules) {
+ EnableLogging(aModules.get());
+}
+
+void logging::CheckEnv() { EnableLogging(PR_GetEnv("A11YLOG")); }
diff --git a/accessible/base/Logging.h b/accessible/base/Logging.h
new file mode 100644
index 0000000000..6e2bc69ec4
--- /dev/null
+++ b/accessible/base/Logging.h
@@ -0,0 +1,230 @@
+/* -*- 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_logs_h__
+#define mozilla_a11y_logs_h__
+
+#include "nscore.h"
+#include "nsStringFwd.h"
+#include "mozilla/Attributes.h"
+
+class nsINode;
+class nsIRequest;
+class nsISupports;
+class nsIWebProgress;
+
+namespace mozilla {
+
+namespace dom {
+class Document;
+class Selection;
+} // namespace dom
+
+namespace a11y {
+
+class AccEvent;
+class Accessible;
+class DocAccessible;
+class OuterDocAccessible;
+
+namespace logging {
+
+enum EModules {
+ eDocLoad = 1 << 0,
+ eDocCreate = 1 << 1,
+ eDocDestroy = 1 << 2,
+ eDocLifeCycle = eDocLoad | eDocCreate | eDocDestroy,
+
+ eEvents = 1 << 3,
+ eEventTree = 1 << 4,
+ ePlatforms = 1 << 5,
+ eText = 1 << 6,
+ eTree = 1 << 7,
+
+ eDOMEvents = 1 << 8,
+ eFocus = 1 << 9,
+ eSelection = 1 << 10,
+ eNotifications = eDOMEvents | eSelection | eFocus,
+
+ // extras
+ eStack = 1 << 11,
+ eVerbose = 1 << 12
+};
+
+/**
+ * Return true if any of the given modules is logged.
+ */
+bool IsEnabled(uint32_t aModules);
+
+/**
+ * Return true if all of the given modules are logged.
+ */
+bool IsEnabledAll(uint32_t aModules);
+
+/**
+ * Return true if the given module is logged.
+ */
+bool IsEnabled(const nsAString& aModules);
+
+/**
+ * Log the document loading progress.
+ */
+void DocLoad(const char* aMsg, nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags);
+void DocLoad(const char* aMsg, dom::Document* aDocumentNode);
+void DocCompleteLoad(DocAccessible* aDocument, bool aIsLoadEventTarget);
+
+/**
+ * Log that document load event was fired.
+ */
+void DocLoadEventFired(AccEvent* aEvent);
+
+/**
+ * Log that document laod event was handled.
+ */
+void DocLoadEventHandled(AccEvent* aEvent);
+
+/**
+ * Log the document was created.
+ */
+void DocCreate(const char* aMsg, dom::Document* aDocumentNode,
+ DocAccessible* aDocument = nullptr);
+
+/**
+ * Log the document was destroyed.
+ */
+void DocDestroy(const char* aMsg, dom::Document* aDocumentNode,
+ DocAccessible* aDocument = nullptr);
+
+/**
+ * Log the outer document was destroyed.
+ */
+void OuterDocDestroy(OuterDocAccessible* OuterDoc);
+
+/**
+ * Log the focus notification target.
+ */
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ Accessible* aTarget);
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsINode* aTargetNode);
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsISupports* aTargetThing);
+
+/**
+ * Log a cause of active item descendant change (submessage).
+ */
+void ActiveItemChangeCausedBy(const char* aMsg, Accessible* aTarget);
+
+/**
+ * Log the active widget (submessage).
+ */
+void ActiveWidget(Accessible* aWidget);
+
+/**
+ * Log the focus event was dispatched (submessage).
+ */
+void FocusDispatched(Accessible* aTarget);
+
+/**
+ * Log the selection change.
+ */
+void SelChange(dom::Selection* aSelection, DocAccessible* aDocument,
+ int16_t aReason);
+
+/**
+ * Log the given accessible elements info.
+ */
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...);
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags, const char* aMsg1,
+ Accessible* aAcc, const char* aMsg2, nsINode* aNode);
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent);
+
+/**
+ * Log the accessible/DOM tree.
+ */
+typedef const char* (*GetTreePrefix)(void* aData, Accessible*);
+void Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
+ GetTreePrefix aPrefixFunc = nullptr,
+ void* aGetTreePrefixData = nullptr);
+void DOMTree(const char* aTitle, const char* aMsgText, DocAccessible* aDoc);
+
+/**
+ * Log the message ('title: text' format) on new line. Print the start and end
+ * boundaries of the message body designated by '{' and '}' (2 spaces indent for
+ * body).
+ */
+void MsgBegin(const char* aTitle, const char* aMsgText, ...)
+ MOZ_FORMAT_PRINTF(2, 3);
+void MsgEnd();
+
+/**
+ * Print start and end boundaries of the message body designated by '{' and '}'
+ * (2 spaces indent for body).
+ */
+void SubMsgBegin();
+void SubMsgEnd();
+
+/**
+ * Log the entry into message body (4 spaces indent).
+ */
+void MsgEntry(const char* aEntryText, ...) MOZ_FORMAT_PRINTF(1, 2);
+
+/**
+ * Log the text, two spaces offset is used.
+ */
+void Text(const char* aText);
+
+/**
+ * Log the accessible object address as message entry (4 spaces indent).
+ */
+void Address(const char* aDescr, Accessible* aAcc);
+
+/**
+ * Log the DOM node info as message entry.
+ */
+void Node(const char* aDescr, nsINode* aNode);
+
+/**
+ * Log the document accessible info as message entry.
+ */
+void Document(DocAccessible* aDocument);
+
+/**
+ * Log the accessible and its DOM node as a message entry.
+ */
+void AccessibleInfo(const char* aDescr, Accessible* aAccessible);
+void AccessibleNNode(const char* aDescr, Accessible* aAccessible);
+void AccessibleNNode(const char* aDescr, nsINode* aNode);
+
+/**
+ * Log the DOM event.
+ */
+void DOMEvent(const char* aDescr, nsINode* aOrigTarget,
+ const nsAString& aEventType);
+
+/**
+ * Log the call stack, two spaces offset is used.
+ */
+void Stack();
+
+/**
+ * Enable logging of the specified modules, all other modules aren't logged.
+ */
+void Enable(const nsCString& aModules);
+
+/**
+ * Enable logging of modules specified by A11YLOG environment variable,
+ * all other modules aren't logged.
+ */
+void CheckEnv();
+
+} // namespace logging
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/MarkupMap.h b/accessible/base/MarkupMap.h
new file mode 100644
index 0000000000..3adfd9ec1d
--- /dev/null
+++ b/accessible/base/MarkupMap.h
@@ -0,0 +1,561 @@
+/* -*- 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/. */
+
+MARKUPMAP(
+ a,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // Only some roles truly enjoy life as HTMLLinkAccessibles, for
+ // details see closed bug 494807.
+ const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aElement);
+ if (roleMapEntry && roleMapEntry->role != roles::NOTHING &&
+ roleMapEntry->role != roles::LINK) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+ }
+
+ return new HTMLLinkAccessible(aElement, aContext->Document());
+ },
+ roles::LINK)
+
+MARKUPMAP(abbr, New_HyperText, 0)
+
+MARKUPMAP(acronym, New_HyperText, 0)
+
+MARKUPMAP(article, New_HyperText, roles::ARTICLE, Attr(xmlroles, article))
+
+MARKUPMAP(aside, New_HyperText, roles::LANDMARK)
+
+MARKUPMAP(blockquote, New_HyperText, roles::BLOCKQUOTE)
+
+MARKUPMAP(
+ button,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLButtonAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ caption,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aContext->IsTable()) {
+ dom::HTMLTableElement* tableEl =
+ dom::HTMLTableElement::FromNode(aContext->GetContent());
+ if (tableEl && tableEl == aElement->GetParent() &&
+ tableEl->GetCaption() == aElement) {
+ return new HTMLCaptionAccessible(aElement, aContext->Document());
+ }
+ }
+ return nullptr;
+ },
+ 0)
+
+// XXX: Uncomment this once HTML-aam agrees to map to same as ARIA.
+// MARKUPMAP(code, New_HyperText, roles::CODE)
+
+MARKUPMAP(dd, New_HTMLDtOrDd<HyperTextAccessibleWrap>, roles::DEFINITION)
+
+MARKUPMAP(del, New_HyperText, roles::CONTENT_DELETION)
+
+MARKUPMAP(details, New_HyperText, roles::DETAILS)
+
+MARKUPMAP(dialog, New_HyperText, roles::DIALOG)
+
+MARKUPMAP(
+ div,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // Never create an accessible if we're part of an anonymous
+ // subtree.
+ if (aElement->IsInNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+ // Always create an accessible if the div has an id.
+ if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id)) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+ }
+ // Never create an accessible if the div is not display:block; or
+ // display:inline-block;
+ nsAutoString displayValue;
+ StyleInfo styleInfo(aElement);
+ styleInfo.Display(displayValue);
+ if (displayValue != u"block"_ns && displayValue != u"inline-block"_ns) {
+ return nullptr;
+ }
+ // Check for various conditions to determine if this is a block
+ // break and needs to be rendered.
+ // If its previous sibling is an inline element, we probably want
+ // to break, so render.
+ nsIContent* prevSibling = aElement->GetPreviousSibling();
+ if (prevSibling) {
+ nsIFrame* prevSiblingFrame = prevSibling->GetPrimaryFrame();
+ if (prevSiblingFrame && prevSiblingFrame->IsInlineOutside()) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+ }
+ }
+ // Now, check the children.
+ nsIContent* firstChild = aElement->GetFirstChild();
+ if (firstChild) {
+ nsIFrame* firstChildFrame = firstChild->GetPrimaryFrame();
+ if (!firstChildFrame) {
+ // The first child is invisible, but this might be due to an
+ // invisible text node. Try the next.
+ firstChild = firstChild->GetNextSibling();
+ if (!firstChild) {
+ // If there's no next sibling, there's only one child, so there's
+ // nothing more we can do.
+ return nullptr;
+ }
+ firstChildFrame = firstChild->GetPrimaryFrame();
+ }
+ // Check to see if first child has an inline frame.
+ if (firstChildFrame && firstChildFrame->IsInlineOutside()) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+ }
+ nsIContent* lastChild = aElement->GetLastChild();
+ MOZ_ASSERT(lastChild);
+ if (lastChild != firstChild) {
+ nsIFrame* lastChildFrame = lastChild->GetPrimaryFrame();
+ if (!lastChildFrame) {
+ // The last child is invisible, but this might be due to an
+ // invisible text node. Try the next.
+ lastChild = lastChild->GetPreviousSibling();
+ MOZ_ASSERT(lastChild);
+ if (lastChild == firstChild) {
+ return nullptr;
+ }
+ lastChildFrame = lastChild->GetPrimaryFrame();
+ }
+ // Check to see if last child has an inline frame.
+ if (lastChildFrame && lastChildFrame->IsInlineOutside()) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+ }
+ }
+ }
+ return nullptr;
+ },
+ roles::SECTION)
+
+MARKUPMAP(
+ dl,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLListAccessible(aElement, aContext->Document());
+ },
+ roles::DEFINITION_LIST)
+
+MARKUPMAP(dt, New_HTMLDtOrDd<HTMLLIAccessible>, roles::TERM)
+
+MARKUPMAP(
+ figcaption,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLFigcaptionAccessible(aElement, aContext->Document());
+ },
+ roles::CAPTION)
+
+MARKUPMAP(
+ figure,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLFigureAccessible(aElement, aContext->Document());
+ },
+ roles::FIGURE, Attr(xmlroles, figure))
+
+MARKUPMAP(
+ fieldset,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLGroupboxAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ form,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLFormAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ footer,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLHeaderOrFooterAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ header,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLHeaderOrFooterAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(h1, New_HyperText, roles::HEADING)
+
+MARKUPMAP(h2, New_HyperText, roles::HEADING)
+
+MARKUPMAP(h3, New_HyperText, roles::HEADING)
+
+MARKUPMAP(h4, New_HyperText, roles::HEADING)
+
+MARKUPMAP(h5, New_HyperText, roles::HEADING)
+
+MARKUPMAP(h6, New_HyperText, roles::HEADING)
+
+MARKUPMAP(
+ hr,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLHRAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ input,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // TODO(emilio): This would be faster if it used
+ // HTMLInputElement's already-parsed representation.
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::checkbox, eIgnoreCase)) {
+ return new CheckboxAccessible(aElement, aContext->Document());
+ }
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::image, eIgnoreCase)) {
+ return new HTMLButtonAccessible(aElement, aContext->Document());
+ }
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::radio, eIgnoreCase)) {
+ return new HTMLRadioButtonAccessible(aElement, aContext->Document());
+ }
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::time, eIgnoreCase)) {
+ return new HTMLDateTimeAccessible<roles::TIME_EDITOR>(
+ aElement, aContext->Document());
+ }
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::date, eIgnoreCase)) {
+ return new HTMLDateTimeAccessible<roles::DATE_EDITOR>(
+ aElement, aContext->Document());
+ }
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(ins, New_HyperText, roles::CONTENT_INSERTION)
+
+MARKUPMAP(
+ label,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLLabelAccessible(aElement, aContext->Document());
+ },
+ roles::LABEL)
+
+MARKUPMAP(
+ legend,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLLegendAccessible(aElement, aContext->Document());
+ },
+ roles::LABEL)
+
+MARKUPMAP(
+ li,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // If list item is a child of accessible list then create an
+ // accessible for it unconditionally by tag name. nsBlockFrame
+ // creates the list item accessible for other elements styled as
+ // list items.
+ if (aContext->IsList() &&
+ aContext->GetContent() == aElement->GetParent()) {
+ return new HTMLLIAccessible(aElement, aContext->Document());
+ }
+
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(main, New_HyperText, roles::LANDMARK)
+
+MARKUPMAP(map, nullptr, roles::TEXT_CONTAINER)
+
+MARKUPMAP(mark, New_HyperText, roles::MARK, Attr(xmlroles, mark))
+
+MARKUPMAP(math, New_HyperText, roles::MATHML_MATH)
+
+MARKUPMAP(mi_, New_HyperText, roles::MATHML_IDENTIFIER)
+
+MARKUPMAP(mn_, New_HyperText, roles::MATHML_NUMBER)
+
+MARKUPMAP(mo_, New_HyperText, roles::MATHML_OPERATOR,
+ AttrFromDOM(accent_, accent_), AttrFromDOM(fence_, fence_),
+ AttrFromDOM(separator_, separator_), AttrFromDOM(largeop_, largeop_))
+
+MARKUPMAP(mtext_, New_HyperText, roles::MATHML_TEXT)
+
+MARKUPMAP(ms_, New_HyperText, roles::MATHML_STRING_LITERAL)
+
+MARKUPMAP(mglyph_, New_HyperText, roles::MATHML_GLYPH)
+
+MARKUPMAP(mrow_, New_HyperText, roles::MATHML_ROW)
+
+MARKUPMAP(mfrac_, New_HyperText, roles::MATHML_FRACTION,
+ AttrFromDOM(bevelled_, bevelled_),
+ AttrFromDOM(linethickness_, linethickness_))
+
+MARKUPMAP(msqrt_, New_HyperText, roles::MATHML_SQUARE_ROOT)
+
+MARKUPMAP(mroot_, New_HyperText, roles::MATHML_ROOT)
+
+MARKUPMAP(mfenced_, New_HyperText, roles::MATHML_FENCED,
+ AttrFromDOM(close, close), AttrFromDOM(open, open),
+ AttrFromDOM(separators_, separators_))
+
+MARKUPMAP(menclose_, New_HyperText, roles::MATHML_ENCLOSED,
+ AttrFromDOM(notation_, notation_))
+
+MARKUPMAP(mstyle_, New_HyperText, roles::MATHML_STYLE)
+
+MARKUPMAP(msub_, New_HyperText, roles::MATHML_SUB)
+
+MARKUPMAP(msup_, New_HyperText, roles::MATHML_SUP)
+
+MARKUPMAP(msubsup_, New_HyperText, roles::MATHML_SUB_SUP)
+
+MARKUPMAP(munder_, New_HyperText, roles::MATHML_UNDER,
+ AttrFromDOM(accentunder_, accentunder_), AttrFromDOM(align, align))
+
+MARKUPMAP(mover_, New_HyperText, roles::MATHML_OVER,
+ AttrFromDOM(accent_, accent_), AttrFromDOM(align, align))
+
+MARKUPMAP(munderover_, New_HyperText, roles::MATHML_UNDER_OVER,
+ AttrFromDOM(accent_, accent_),
+ AttrFromDOM(accentunder_, accentunder_), AttrFromDOM(align, align))
+
+MARKUPMAP(mmultiscripts_, New_HyperText, roles::MATHML_MULTISCRIPTS)
+
+MARKUPMAP(
+ mtable_,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLTableAccessible(aElement, aContext->Document());
+ },
+ roles::MATHML_TABLE, AttrFromDOM(align, align),
+ AttrFromDOM(columnlines_, columnlines_), AttrFromDOM(rowlines_, rowlines_))
+
+MARKUPMAP(
+ mlabeledtr_,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLTableRowAccessible(aElement, aContext->Document());
+ },
+ roles::MATHML_LABELED_ROW)
+
+MARKUPMAP(
+ mtr_,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLTableRowAccessible(aElement, aContext->Document());
+ },
+ roles::MATHML_TABLE_ROW)
+
+MARKUPMAP(
+ mtd_,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLTableCellAccessible(aElement, aContext->Document());
+ },
+ roles::MATHML_CELL)
+
+MARKUPMAP(maction_, New_HyperText, roles::MATHML_ACTION,
+ AttrFromDOM(actiontype_, actiontype_),
+ AttrFromDOM(selection_, selection_))
+
+MARKUPMAP(
+ menu,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLListAccessible(aElement, aContext->Document());
+ },
+ roles::LIST)
+
+MARKUPMAP(merror_, New_HyperText, roles::MATHML_ERROR)
+
+MARKUPMAP(mstack_, New_HyperText, roles::MATHML_STACK,
+ AttrFromDOM(align, align), AttrFromDOM(position, position))
+
+MARKUPMAP(mlongdiv_, New_HyperText, roles::MATHML_LONG_DIVISION,
+ AttrFromDOM(longdivstyle_, longdivstyle_))
+
+MARKUPMAP(msgroup_, New_HyperText, roles::MATHML_STACK_GROUP,
+ AttrFromDOM(position, position), AttrFromDOM(shift_, shift_))
+
+MARKUPMAP(msrow_, New_HyperText, roles::MATHML_STACK_ROW,
+ AttrFromDOM(position, position))
+
+MARKUPMAP(mscarries_, New_HyperText, roles::MATHML_STACK_CARRIES,
+ AttrFromDOM(location_, location_), AttrFromDOM(position, position))
+
+MARKUPMAP(mscarry_, New_HyperText, roles::MATHML_STACK_CARRY,
+ AttrFromDOM(crossout_, crossout_))
+
+MARKUPMAP(msline_, New_HyperText, roles::MATHML_STACK_LINE,
+ AttrFromDOM(position, position))
+
+MARKUPMAP(nav, New_HyperText, roles::LANDMARK)
+
+MARKUPMAP(
+ ol,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLListAccessible(aElement, aContext->Document());
+ },
+ roles::LIST)
+
+MARKUPMAP(
+ option,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLSelectOptionAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ optgroup,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLSelectOptGroupAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ output,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLOutputAccessible(aElement, aContext->Document());
+ },
+ roles::STATUSBAR, Attr(live, polite))
+
+MARKUPMAP(p, nullptr, roles::PARAGRAPH)
+
+MARKUPMAP(
+ progress,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLProgressAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(q, New_HyperText, 0)
+
+MARKUPMAP(
+ section,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLSectionAccessible(aElement, aContext->Document());
+ },
+ 0)
+
+MARKUPMAP(
+ summary,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLSummaryAccessible(aElement, aContext->Document());
+ },
+ roles::SUMMARY)
+
+MARKUPMAP(
+ table,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aElement->GetPrimaryFrame() &&
+ aElement->GetPrimaryFrame()->AccessibleType() != eHTMLTableType) {
+ return new ARIAGridAccessibleWrap(aElement, aContext->Document());
+ }
+
+ // Make sure that our children are proper layout table parts
+ for (nsIContent* child = aElement->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tfoot,
+ nsGkAtoms::tbody, nsGkAtoms::tr)) {
+ // These children elements need to participate in the layout table
+ // and need table row(group) frames.
+ nsIFrame* childFrame = child->GetPrimaryFrame();
+ if (childFrame && (!childFrame->IsTableRowGroupFrame() &&
+ !childFrame->IsTableRowFrame())) {
+ return new ARIAGridAccessibleWrap(aElement, aContext->Document());
+ }
+ }
+ }
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(time, New_HyperText, 0, Attr(xmlroles, time),
+ AttrFromDOM(datetime, datetime))
+
+MARKUPMAP(tbody, nullptr, roles::GROUPING)
+
+MARKUPMAP(
+ td,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aContext->IsTableRow() &&
+ aContext->GetContent() == aElement->GetParent()) {
+ // If HTML:td element is part of its HTML:table, which has CSS
+ // display style other than 'table', then create a generic table
+ // cell accessible, because there's no underlying table layout and
+ // thus native HTML table cell class doesn't work. The same is
+ // true if the cell itself has CSS display:block;.
+ if (!aContext->IsHTMLTableRow() ||
+ (aElement->GetPrimaryFrame() &&
+ aElement->GetPrimaryFrame()->AccessibleType() !=
+ eHTMLTableCellType)) {
+ return new ARIAGridCellAccessibleWrap(aElement, aContext->Document());
+ }
+ if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::scope)) {
+ return new HTMLTableHeaderCellAccessibleWrap(aElement,
+ aContext->Document());
+ }
+ }
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(tfoot, nullptr, roles::GROUPING)
+
+MARKUPMAP(
+ th,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aContext->IsTableRow() &&
+ aContext->GetContent() == aElement->GetParent()) {
+ if (!aContext->IsHTMLTableRow()) {
+ return new ARIAGridCellAccessibleWrap(aElement, aContext->Document());
+ }
+ return new HTMLTableHeaderCellAccessibleWrap(aElement,
+ aContext->Document());
+ }
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(thead, nullptr, roles::GROUPING)
+
+MARKUPMAP(
+ tr,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // If HTML:tr element is part of its HTML:table, which has CSS
+ // display style other than 'table', then create a generic table row
+ // accessible, because there's no underlying table layout and thus
+ // native HTML table row class doesn't work. Refer to
+ // CreateAccessibleByFrameType dual logic.
+ Accessible* table = aContext->IsTable() ? aContext : nullptr;
+ if (!table && aContext->Parent() && aContext->Parent()->IsTable()) {
+ table = aContext->Parent();
+ }
+ if (table) {
+ nsIContent* parentContent = aElement->GetParent();
+ nsIFrame* parentFrame = parentContent->GetPrimaryFrame();
+ if (parentFrame && !parentFrame->IsTableWrapperFrame()) {
+ parentContent = parentContent->GetParent();
+ parentFrame = parentContent->GetPrimaryFrame();
+ if (table->GetContent() == parentContent &&
+ ((parentFrame && !parentFrame->IsTableWrapperFrame()) ||
+ (aElement->GetPrimaryFrame() &&
+ aElement->GetPrimaryFrame()->AccessibleType() !=
+ eHTMLTableRowType))) {
+ return new ARIARowAccessible(aElement, aContext->Document());
+ }
+ }
+ }
+ return nullptr;
+ },
+ 0)
+
+MARKUPMAP(
+ ul,
+ [](Element* aElement, Accessible* aContext) -> Accessible* {
+ return new HTMLListAccessible(aElement, aContext->Document());
+ },
+ roles::LIST)
diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp
new file mode 100644
index 0000000000..d5a678e321
--- /dev/null
+++ b/accessible/base/NotificationController.cpp
@@ -0,0 +1,974 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NotificationController.h"
+
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "GeckoProfiler.h"
+#include "nsEventShell.h"
+#include "TextLeafAccessible.h"
+#include "TextUpdater.h"
+
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector
+////////////////////////////////////////////////////////////////////////////////
+
+NotificationController::NotificationController(DocAccessible* aDocument,
+ PresShell* aPresShell)
+ : EventQueue(aDocument),
+ mObservingState(eNotObservingRefresh),
+ mPresShell(aPresShell),
+ mEventGeneration(0) {
+#ifdef DEBUG
+ mMoveGuardOnStack = false;
+#endif
+
+ // Schedule initial accessible tree construction.
+ ScheduleProcessing();
+}
+
+NotificationController::~NotificationController() {
+ NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
+ if (mDocument) Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: AddRef/Release and cycle collection
+
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
+ if (tmp->mDocument) tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
+ for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
+ cb.NoteXPCOMChild(it.Key());
+ nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
+ for (uint32_t i = 0; i < list->Length(); i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions value item");
+ cb.NoteXPCOMChild(list->ElementAt(i));
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: public
+
+void NotificationController::Shutdown() {
+ if (mObservingState != eNotObservingRefresh &&
+ mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
+ mObservingState = eNotObservingRefresh;
+ }
+
+ // Shutdown handling child documents.
+ int32_t childDocCount = mHangingChildDocuments.Length();
+ for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
+ if (!mHangingChildDocuments[idx]->IsDefunct())
+ mHangingChildDocuments[idx]->Shutdown();
+ }
+
+ mHangingChildDocuments.Clear();
+
+ mDocument = nullptr;
+ mPresShell = nullptr;
+
+ mTextHash.Clear();
+ mContentInsertions.Clear();
+ mNotifications.Clear();
+ mEvents.Clear();
+ mRelocations.Clear();
+ mEventTree.Clear();
+}
+
+EventTree* NotificationController::QueueMutation(Accessible* aContainer) {
+ EventTree* tree = mEventTree.FindOrInsert(aContainer);
+ if (tree) {
+ ScheduleProcessing();
+ }
+ return tree;
+}
+
+bool NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent) {
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
+ // We have to allow there to be a hide and then a show event for a target
+ // because of targets getting moved. However we need to coalesce a show and
+ // then a hide for a target which means we need to check for that here.
+ if (aEvent->GetAccessible()->ShowEventTarget()) {
+ AccTreeMutationEvent* showEvent =
+ mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
+ DropMutationEvent(showEvent);
+ return false;
+ }
+
+ // If this is an additional hide event, the accessible may be hidden, or
+ // moved again after a move. Preserve the original hide event since
+ // its properties are consistent with the tree that existed before
+ // the next batch of mutation events is processed.
+ if (aEvent->GetAccessible()->HideEventTarget()) {
+ return false;
+ }
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
+ mEventGeneration++;
+ mutEvent->SetEventGeneration(mEventGeneration);
+
+ if (!mFirstMutationEvent) {
+ mFirstMutationEvent = aEvent;
+ ScheduleProcessing();
+ }
+
+ if (mLastMutationEvent) {
+ NS_ASSERTION(!mLastMutationEvent->NextEvent(),
+ "why isn't the last event the end?");
+ mLastMutationEvent->SetNextEvent(aEvent);
+ }
+
+ aEvent->SetPrevEvent(mLastMutationEvent);
+ mLastMutationEvent = aEvent;
+ mMutationMap.PutEvent(aEvent);
+
+ // Because we could be hiding the target of a show event we need to get rid
+ // of any such events. It may be possible to do less than coallesce all
+ // events, however that is easiest.
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
+ CoalesceMutationEvents();
+
+ // mLastMutationEvent will point to something other than aEvent if and only
+ // if aEvent was just coalesced away. In that case a parent accessible
+ // must already have the required reorder and text change events so we are
+ // done here.
+ if (mLastMutationEvent != aEvent) {
+ return false;
+ }
+ }
+
+ // We need to fire a reorder event after all of the events targeted at shown
+ // or hidden children of a container. So either queue a new one, or move an
+ // existing one to the end of the queue if the container already has a
+ // reorder event.
+ Accessible* target = aEvent->GetAccessible();
+ Accessible* container = aEvent->GetAccessible()->Parent();
+ RefPtr<AccReorderEvent> reorder;
+ if (!container->ReorderEventTarget()) {
+ reorder = new AccReorderEvent(container);
+ container->SetReorderEventTarget(true);
+ mMutationMap.PutEvent(reorder);
+
+ // Since this is the first child of container that is changing, the name of
+ // container may be changing.
+ QueueNameChange(target);
+ } else {
+ AccReorderEvent* event = downcast_accEvent(
+ mMutationMap.GetEvent(container, EventMap::ReorderEvent));
+ reorder = event;
+ if (mFirstMutationEvent == event) {
+ mFirstMutationEvent = event->NextEvent();
+ } else {
+ event->PrevEvent()->SetNextEvent(event->NextEvent());
+ }
+
+ event->NextEvent()->SetPrevEvent(event->PrevEvent());
+ event->SetNextEvent(nullptr);
+ }
+
+ reorder->SetEventGeneration(mEventGeneration);
+ reorder->SetPrevEvent(mLastMutationEvent);
+ mLastMutationEvent->SetNextEvent(reorder);
+ mLastMutationEvent = reorder;
+
+ // It is not possible to have a text change event for something other than a
+ // hyper text accessible.
+ if (!container->IsHyperText()) {
+ return true;
+ }
+
+ MOZ_ASSERT(mutEvent);
+
+ nsString text;
+ aEvent->GetAccessible()->AppendTextTo(text);
+ if (text.IsEmpty()) {
+ return true;
+ }
+
+ int32_t offset = container->AsHyperText()->GetChildOffset(target);
+ AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
+ while (prevEvent &&
+ prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ prevEvent = prevEvent->PrevEvent();
+ }
+
+ if (prevEvent &&
+ prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
+ mutEvent->IsHide()) {
+ AccHideEvent* prevHide = downcast_accEvent(prevEvent);
+ AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
+ if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
+ if (prevHide->mNextSibling == target) {
+ target->AppendTextTo(prevTextChange->mModifiedText);
+ prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ } else if (prevHide->mPrevSibling == target) {
+ nsString temp;
+ target->AppendTextTo(temp);
+
+ uint32_t extraLen = temp.Length();
+ temp += prevTextChange->mModifiedText;
+ ;
+ prevTextChange->mModifiedText = temp;
+ prevTextChange->mStart -= extraLen;
+ prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ }
+ }
+ } else if (prevEvent && mutEvent->IsShow() &&
+ prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ AccShowEvent* prevShow = downcast_accEvent(prevEvent);
+ AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
+ if (prevTextChange && prevShow->Parent() == target->Parent()) {
+ int32_t index = target->IndexInParent();
+ int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
+ if (prevIndex + 1 == index) {
+ target->AppendTextTo(prevTextChange->mModifiedText);
+ prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ } else if (index + 1 == prevIndex) {
+ nsString temp;
+ target->AppendTextTo(temp);
+ prevTextChange->mStart -= temp.Length();
+ temp += prevTextChange->mModifiedText;
+ prevTextChange->mModifiedText = temp;
+ prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ }
+ }
+ }
+
+ if (!mutEvent->mTextChangeEvent) {
+ mutEvent->mTextChangeEvent = new AccTextChangeEvent(
+ container, offset, text, mutEvent->IsShow(),
+ aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
+ }
+
+ return true;
+}
+
+void NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent) {
+ // unset the event bits since the event isn't being fired any more.
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ aEvent->GetAccessible()->SetReorderEventTarget(false);
+ } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ aEvent->GetAccessible()->SetShowEventTarget(false);
+ } else {
+ aEvent->GetAccessible()->SetHideEventTarget(false);
+
+ AccHideEvent* hideEvent = downcast_accEvent(aEvent);
+ MOZ_ASSERT(hideEvent);
+
+ if (hideEvent->NeedsShutdown()) {
+ mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
+ }
+ }
+
+ // Do the work to splice the event out of the list.
+ if (mFirstMutationEvent == aEvent) {
+ mFirstMutationEvent = aEvent->NextEvent();
+ } else {
+ aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
+ }
+
+ if (mLastMutationEvent == aEvent) {
+ mLastMutationEvent = aEvent->PrevEvent();
+ } else {
+ aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
+ }
+
+ aEvent->SetPrevEvent(nullptr);
+ aEvent->SetNextEvent(nullptr);
+ mMutationMap.RemoveEvent(aEvent);
+}
+
+void NotificationController::CoalesceMutationEvents() {
+ AccTreeMutationEvent* event = mFirstMutationEvent;
+ while (event) {
+ AccTreeMutationEvent* nextEvent = event->NextEvent();
+ uint32_t eventType = event->GetEventType();
+ if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ Accessible* acc = event->GetAccessible();
+ while (acc) {
+ if (acc->IsDoc()) {
+ break;
+ }
+
+ // if a parent of the reorder event's target is being hidden that
+ // hide event's target must have a parent that is also a reorder event
+ // target. That means we don't need this reorder event.
+ if (acc->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ Accessible* parent = acc->Parent();
+ if (parent->ReorderEventTarget()) {
+ AccReorderEvent* reorder = downcast_accEvent(
+ mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
+
+ // We want to make sure that a reorder event comes after any show or
+ // hide events targeted at the children of its target. We keep the
+ // invariant that event generation goes up as you are farther in the
+ // queue, so we want to use the spot of the event with the higher
+ // generation number, and keep that generation number.
+ if (reorder &&
+ reorder->EventGeneration() < event->EventGeneration()) {
+ reorder->SetEventGeneration(event->EventGeneration());
+
+ // It may be true that reorder was before event, and we coalesced
+ // away all the show / hide events between them. In that case
+ // event is already immediately after reorder in the queue and we
+ // do not need to rearrange the list of events.
+ if (event != reorder->NextEvent()) {
+ // There really should be a show or hide event before the first
+ // reorder event.
+ if (reorder->PrevEvent()) {
+ reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
+ } else {
+ mFirstMutationEvent = reorder->NextEvent();
+ }
+
+ reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
+ event->PrevEvent()->SetNextEvent(reorder);
+ reorder->SetPrevEvent(event->PrevEvent());
+ event->SetPrevEvent(reorder);
+ reorder->SetNextEvent(event);
+ }
+ }
+ DropMutationEvent(event);
+ break;
+ }
+
+ acc = parent;
+ }
+ } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
+ Accessible* parent = event->GetAccessible()->Parent();
+ while (parent) {
+ if (parent->IsDoc()) {
+ break;
+ }
+
+ // if the parent of a show event is being either shown or hidden then
+ // we don't need to fire a show event for a subtree of that change.
+ if (parent->ShowEventTarget() || parent->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ parent = parent->Parent();
+ }
+ } else {
+ MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE,
+ "mutation event list has an invalid event");
+
+ AccHideEvent* hideEvent = downcast_accEvent(event);
+ Accessible* parent = hideEvent->Parent();
+ while (parent) {
+ if (parent->IsDoc()) {
+ break;
+ }
+
+ if (parent->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ if (parent->ShowEventTarget()) {
+ AccShowEvent* showEvent = downcast_accEvent(
+ mMutationMap.GetEvent(parent, EventMap::ShowEvent));
+ if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
+ DropMutationEvent(hideEvent);
+ break;
+ }
+ }
+
+ parent = parent->Parent();
+ }
+ }
+
+ event = nextEvent;
+ }
+}
+
+void NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument) {
+ // Schedule child document binding to the tree.
+ mHangingChildDocuments.AppendElement(aDocument);
+ ScheduleProcessing();
+}
+
+void NotificationController::ScheduleContentInsertion(
+ Accessible* aContainer, nsTArray<nsCOMPtr<nsIContent>>& aInsertions) {
+ if (!aInsertions.IsEmpty()) {
+ mContentInsertions.LookupOrAdd(aContainer)->AppendElements(aInsertions);
+ ScheduleProcessing();
+ }
+}
+
+void NotificationController::ScheduleProcessing() {
+ // If notification flush isn't planed yet start notification flush
+ // asynchronously (after style and layout).
+ if (mObservingState == eNotObservingRefresh) {
+ if (mPresShell->AddRefreshObserver(this, FlushType::Display,
+ "Accessibility notifications"))
+ mObservingState = eRefreshObserving;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: protected
+
+bool NotificationController::IsUpdatePending() {
+ return mPresShell->IsLayoutFlushObserver() ||
+ mObservingState == eRefreshProcessingForUpdate || WaitingForParent() ||
+ mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
+ mTextHash.Count() != 0 ||
+ !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
+}
+
+bool NotificationController::WaitingForParent() {
+ DocAccessible* parentdoc = mDocument->ParentDocument();
+ if (!parentdoc) {
+ return false;
+ }
+
+ NotificationController* parent = parentdoc->mNotificationController;
+ if (!parent || parent == this) {
+ // Do not wait for nothing or ourselves
+ return false;
+ }
+
+ // Wait for parent's notifications processing
+ return parent->mContentInsertions.Count() != 0 ||
+ parent->mNotifications.Length() != 0;
+}
+
+void NotificationController::ProcessMutationEvents() {
+ // there is no reason to fire a hide event for a child of a show event
+ // target. That can happen if something is inserted into the tree and
+ // removed before the next refresh driver tick, but it should not be
+ // observable outside gecko so it should be safe to coalesce away any such
+ // events. This means that it should be fine to fire all of the hide events
+ // first, and then deal with any shown subtrees.
+ for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
+ event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
+ continue;
+ }
+
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(event);
+ if (mutEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ // Fire menupopup end event before a hide event if a menu goes away.
+
+ // XXX: We don't look into children of hidden subtree to find hiding
+ // menupopup (as we did prior bug 570275) because we don't do that when
+ // menu is showing (and that's impossible until bug 606924 is fixed).
+ // Nevertheless we should do this at least because layout coalesces
+ // the changes before our processing and we may miss some menupopup
+ // events. Now we just want to be consistent in content insertion/removal
+ // handling.
+ if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ event->mAccessible);
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ AccHideEvent* hideEvent = downcast_accEvent(event);
+ if (hideEvent->NeedsShutdown()) {
+ mDocument->ShutdownChildrenInSubtree(event->mAccessible);
+ }
+ }
+
+ // Group the show events by the parent of their target.
+ nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>>
+ showEvents;
+ for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
+ event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
+ continue;
+ }
+
+ Accessible* parent = event->GetAccessible()->Parent();
+ showEvents.GetOrInsert(parent).AppendElement(event);
+ }
+
+ // We need to fire show events for the children of an accessible in the order
+ // of their indices at this point. So sort each set of events for the same
+ // container by the index of their target.
+ for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
+ struct AccIdxComparator {
+ bool LessThan(const AccTreeMutationEvent* a,
+ const AccTreeMutationEvent* b) const {
+ int32_t aIdx = a->GetAccessible()->IndexInParent();
+ int32_t bIdx = b->GetAccessible()->IndexInParent();
+ MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
+ return aIdx < bIdx;
+ }
+ bool Equals(const AccTreeMutationEvent* a,
+ const AccTreeMutationEvent* b) const {
+ DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
+ DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
+ MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
+ return false;
+ }
+ };
+
+ nsTArray<AccTreeMutationEvent*>& events = iter.Data();
+ events.Sort(AccIdxComparator());
+ for (AccTreeMutationEvent* event : events) {
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(event);
+ if (mutEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
+ if (!mDocument) {
+ return;
+ }
+ }
+ }
+ }
+
+ // Now we can fire the reorder events after all the show and hide events.
+ for (AccTreeMutationEvent* event = mFirstMutationEvent; event;
+ event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
+ continue;
+ }
+
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ Accessible* target = event->GetAccessible();
+ target->Document()->MaybeNotifyOfValueChange(target);
+ if (!mDocument) {
+ return;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: private
+
+void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
+ Telemetry::AutoTimer<Telemetry::A11Y_TREE_UPDATE_TIMING_MS> timer;
+
+ AUTO_PROFILER_LABEL("NotificationController::WillRefresh", OTHER);
+
+ // If the document accessible that notification collector was created for is
+ // now shut down, don't process notifications anymore.
+ NS_ASSERTION(
+ mDocument,
+ "The document was shut down while refresh observer is attached!");
+ if (!mDocument) return;
+
+ // Wait until an update, we have started, or an interruptible reflow is
+ // finished.
+ if (mObservingState == eRefreshProcessing ||
+ mObservingState == eRefreshProcessingForUpdate ||
+ mPresShell->IsReflowInterrupted()) {
+ return;
+ }
+
+ // Process parent's notifications before ours, to get proper ordering between
+ // e.g. tab event and content event.
+ if (WaitingForParent()) {
+ mDocument->ParentDocument()->mNotificationController->WillRefresh(aTime);
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ // Any generic notifications should be queued if we're processing content
+ // insertions or generic notifications.
+ mObservingState = eRefreshProcessingForUpdate;
+
+ // Initial accessible tree construction.
+ if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
+ // If document is not bound to parent at this point then the document is not
+ // ready yet (process notifications later).
+ if (!mDocument->IsBoundToParent()) {
+ mObservingState = eRefreshObserving;
+ return;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "initial tree created");
+ logging::Address("document", mDocument);
+ logging::MsgEnd();
+ }
+#endif
+
+ mDocument->DoInitialUpdate();
+
+ NS_ASSERTION(mContentInsertions.Count() == 0,
+ "Pending content insertions while initial accessible tree "
+ "isn't created!");
+ }
+
+ // Process rendered text change notifications.
+ for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
+ nsIContent* textNode = entry->GetKey();
+ Accessible* textAcc = mDocument->GetAccessible(textNode);
+
+ // If the text node is not in tree or doesn't have a frame, or placed in
+ // another document, then this case should have been handled already by
+ // content removal notifications.
+ nsINode* containerNode = textNode->GetFlattenedTreeParentNode();
+ if (!containerNode || textNode->OwnerDoc() != mDocument->DocumentNode()) {
+ MOZ_ASSERT(!textAcc,
+ "Text node was removed but accessible is kept alive!");
+ continue;
+ }
+
+ nsIFrame* textFrame = textNode->GetPrimaryFrame();
+ if (!textFrame) {
+ MOZ_ASSERT(!textAcc,
+ "Text node isn't rendered but accessible is kept alive!");
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ nsIContent* containerElm =
+ containerNode->IsElement() ? containerNode->AsElement() : nullptr;
+#endif
+
+ nsIFrame::RenderedText text = textFrame->GetRenderedText(
+ 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
+ nsIFrame::TrailingWhitespace::DontTrim);
+
+ // Remove text accessible if rendered text is empty.
+ if (textAcc) {
+ if (text.mString.IsEmpty()) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree | logging::eText)) {
+ logging::MsgBegin("TREE", "text node lost its content; doc: %p",
+ mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ mDocument->ContentRemoved(textAcc);
+ continue;
+ }
+
+ // Update text of the accessible and fire text change events.
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eText)) {
+ logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEntry(
+ "old text '%s'",
+ NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
+ logging::MsgEntry("new text: '%s'",
+ NS_ConvertUTF16toUTF8(text.mString).get());
+ logging::MsgEnd();
+ }
+#endif
+
+ TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
+ continue;
+ }
+
+ // Append an accessible if rendered text is not empty.
+ if (!text.mString.IsEmpty()) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree | logging::eText)) {
+ logging::MsgBegin("TREE", "text node gains new content; doc: %p",
+ mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ MOZ_ASSERT(mDocument->AccessibleOrTrueContainer(containerNode),
+ "Text node having rendered text hasn't accessible document!");
+
+ Accessible* container =
+ mDocument->AccessibleOrTrueContainer(containerNode, true);
+ if (container) {
+ nsTArray<nsCOMPtr<nsIContent>>* list =
+ mContentInsertions.LookupOrAdd(container);
+ list->AppendElement(textNode);
+ }
+ }
+ }
+ mTextHash.Clear();
+
+ // Process content inserted notifications to update the tree.
+ for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
+ mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
+ if (!mDocument) {
+ return;
+ }
+ }
+ mContentInsertions.Clear();
+
+ // Bind hanging child documents unless we are using IPC and the
+ // document has no IPC actor. If we fail to bind the child doc then
+ // shut it down.
+ uint32_t hangingDocCnt = mHangingChildDocuments.Length();
+ nsTArray<RefPtr<DocAccessible>> newChildDocs;
+ for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
+ DocAccessible* childDoc = mHangingChildDocuments[idx];
+ if (childDoc->IsDefunct()) continue;
+
+ if (IPCAccessibilityActive() && !mDocument->IPCDoc()) {
+ childDoc->Shutdown();
+ continue;
+ }
+
+ nsIContent* ownerContent =
+ mDocument->DocumentNode()->FindContentForSubDocument(
+ childDoc->DocumentNode());
+ if (ownerContent) {
+ Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
+ if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
+ if (mDocument->AppendChildDocument(childDoc)) {
+ newChildDocs.AppendElement(std::move(mHangingChildDocuments[idx]));
+ continue;
+ }
+
+ outerDocAcc->RemoveChild(childDoc);
+ }
+
+ // Failed to bind the child document, destroy it.
+ childDoc->Shutdown();
+ }
+ }
+
+ // Clear the hanging documents list, even if we didn't bind them.
+ mHangingChildDocuments.Clear();
+ MOZ_ASSERT(mDocument, "Illicit document shutdown");
+ if (!mDocument) {
+ return;
+ }
+
+ // If the document is ready and all its subdocuments are completely loaded
+ // then process the document load.
+ if (mDocument->HasLoadState(DocAccessible::eReady) &&
+ !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
+ hangingDocCnt == 0) {
+ uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
+ for (; childDocIdx < childDocCnt; childDocIdx++) {
+ DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
+ if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded)) break;
+ }
+
+ if (childDocIdx == childDocCnt) {
+ mDocument->ProcessLoad();
+ if (!mDocument) return;
+ }
+ }
+
+ // Process invalidation list of the document after all accessible tree
+ // mutation is done.
+ mDocument->ProcessInvalidationList();
+
+ // Process relocation list.
+ for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
+ // owner should be in a document and have na associated DOM node (docs
+ // sometimes don't)
+ if (mRelocations[idx]->IsInDocument() &&
+ mRelocations[idx]->HasOwnContent()) {
+ mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
+ }
+ }
+ mRelocations.Clear();
+
+ // Process only currently queued generic notifications.
+ // These are used for processing aria-activedescendant, DOMMenuItemActive,
+ // etc. Therefore, they must be processed after relocations, since relocated
+ // subtrees might not have been created before relocation processing and the
+ // target might be inside a relocated subtree.
+ const nsTArray<RefPtr<Notification>> notifications =
+ std::move(mNotifications);
+
+ uint32_t notificationCount = notifications.Length();
+ for (uint32_t idx = 0; idx < notificationCount; idx++) {
+ notifications[idx]->Process();
+ if (!mDocument) return;
+ }
+
+ // If a generic notification occurs after this point then we may be allowed to
+ // process it synchronously. However we do not want to reenter if fireing
+ // events causes script to run.
+ mObservingState = eRefreshProcessing;
+
+ CoalesceMutationEvents();
+ ProcessMutationEvents();
+ mEventGeneration = 0;
+
+ // Now that we are done with them get rid of the events we fired.
+ RefPtr<AccTreeMutationEvent> mutEvent = std::move(mFirstMutationEvent);
+ mLastMutationEvent = nullptr;
+ mFirstMutationEvent = nullptr;
+ while (mutEvent) {
+ RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
+ Accessible* target = mutEvent->GetAccessible();
+
+ // We need to be careful here, while it may seem that we can simply 0 all
+ // the pending event bits that is not true. Because accessibles may be
+ // reparented they may be the target of both a hide event and a show event
+ // at the same time.
+ if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ target->SetShowEventTarget(false);
+ }
+
+ if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
+ target->SetHideEventTarget(false);
+ }
+
+ // However it is not possible for a reorder event target to also be the
+ // target of a show or hide, so we can just zero that.
+ target->SetReorderEventTarget(false);
+
+ mutEvent->SetPrevEvent(nullptr);
+ mutEvent->SetNextEvent(nullptr);
+ mMutationMap.RemoveEvent(mutEvent);
+ mutEvent = nextEvent;
+ }
+
+ ProcessEventQueue();
+
+ if (IPCAccessibilityActive()) {
+ size_t newDocCount = newChildDocs.Length();
+ for (size_t i = 0; i < newDocCount; i++) {
+ DocAccessible* childDoc = newChildDocs[i];
+ if (childDoc->IsDefunct()) {
+ continue;
+ }
+
+ Accessible* parent = childDoc->Parent();
+ DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
+ MOZ_DIAGNOSTIC_ASSERT(parentIPCDoc);
+ uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
+ MOZ_DIAGNOSTIC_ASSERT(id);
+ DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
+ if (ipcDoc) {
+ parentIPCDoc->SendBindChildDoc(ipcDoc, id);
+ continue;
+ }
+
+ ipcDoc = new DocAccessibleChild(childDoc, parentIPCDoc->Manager());
+ childDoc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ parentIPCDoc->ConstructChildDocInParentProcess(
+ ipcDoc, id, AccessibleWrap::GetChildIDFor(childDoc));
+#else
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_GetInterface(mDocument->DocumentNode()->GetDocShell());
+ if (browserChild) {
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
+ ipcDoc->SendPDocAccessiblePlatformExtConstructor();
+ }
+#endif
+ }
+ }
+
+ mObservingState = eRefreshObserving;
+ if (!mDocument) return;
+
+ // Stop further processing if there are no new notifications of any kind or
+ // events and document load is processed.
+ if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
+ mEvents.IsEmpty() && mTextHash.Count() == 0 &&
+ mHangingChildDocuments.IsEmpty() &&
+ mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
+ mPresShell->RemoveRefreshObserver(this, FlushType::Display)) {
+ mObservingState = eNotObservingRefresh;
+ }
+}
+
+void NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent) {
+ EventType type = GetEventType(aEvent);
+ uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+ MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
+ addr |= type;
+ mTable.Put(addr, RefPtr{aEvent});
+}
+
+AccTreeMutationEvent* NotificationController::EventMap::GetEvent(
+ Accessible* aTarget, EventType aType) {
+ uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
+ MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
+
+ addr |= aType;
+ return mTable.GetWeak(addr);
+}
+
+void NotificationController::EventMap::RemoveEvent(
+ AccTreeMutationEvent* aEvent) {
+ EventType type = GetEventType(aEvent);
+ uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+ MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
+ addr |= type;
+
+ MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
+ mTable.Remove(addr);
+}
+
+NotificationController::EventMap::EventType
+NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent) {
+ switch (aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_SHOW:
+ return ShowEvent;
+ case nsIAccessibleEvent::EVENT_HIDE:
+ return HideEvent;
+ case nsIAccessibleEvent::EVENT_REORDER:
+ return ReorderEvent;
+ default:
+ MOZ_ASSERT_UNREACHABLE("event has invalid type");
+ return ShowEvent;
+ }
+}
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
new file mode 100644
index 0000000000..0a5ebee659
--- /dev/null
+++ b/accessible/base/NotificationController.h
@@ -0,0 +1,462 @@
+/* -*- 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 mozilla_a11y_NotificationController_h_
+#define mozilla_a11y_NotificationController_h_
+
+#include "EventQueue.h"
+#include "EventTree.h"
+
+#include "mozilla/Tuple.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefreshObservers.h"
+
+#include <utility>
+
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+class DocAccessible;
+
+/**
+ * Notification interface.
+ */
+class Notification {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::Notification)
+
+ /**
+ * Process notification.
+ */
+ virtual void Process() = 0;
+
+ protected:
+ Notification() {}
+
+ /**
+ * Protected destructor, to discourage deletion outside of Release():
+ */
+ virtual ~Notification() {}
+
+ private:
+ Notification(const Notification&);
+ Notification& operator=(const Notification&);
+};
+
+/**
+ * Template class for generic notification.
+ *
+ * @note Instance is kept as a weak ref, the caller must guarantee it exists
+ * longer than the document accessible owning the notification controller
+ * that this notification is processed by.
+ */
+template <class Class, class... Args>
+class TNotification : public Notification {
+ public:
+ typedef void (Class::*Callback)(Args*...);
+
+ TNotification(Class* aInstance, Callback aCallback, Args*... aArgs)
+ : mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) {}
+ virtual ~TNotification() { mInstance = nullptr; }
+
+ virtual void Process() override {
+ ProcessHelper(std::index_sequence_for<Args...>{});
+ }
+
+ private:
+ TNotification(const TNotification&);
+ TNotification& operator=(const TNotification&);
+
+ template <size_t... Indices>
+ void ProcessHelper(std::index_sequence<Indices...>) {
+ (mInstance->*mCallback)(Get<Indices>(mArgs)...);
+ }
+
+ Class* mInstance;
+ Callback mCallback;
+ Tuple<RefPtr<Args>...> mArgs;
+};
+
+/**
+ * Used to process notifications from core for the document accessible.
+ */
+class NotificationController final : public EventQueue,
+ public nsARefreshObserver {
+ public:
+ NotificationController(DocAccessible* aDocument, PresShell* aPresShell);
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
+
+ /**
+ * Shutdown the notification controller.
+ */
+ void Shutdown();
+
+ /**
+ * Add an accessible event into the queue to process it later.
+ */
+ void QueueEvent(AccEvent* aEvent) {
+ if (PushEvent(aEvent)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Creates and adds a name change event into the queue for a container of
+ * the given accessible, if the accessible is a part of name computation of
+ * the container.
+ */
+ void QueueNameChange(Accessible* aChangeTarget) {
+ if (PushNameChange(aChangeTarget)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Returns existing event tree for the given the accessible or creates one if
+ * it doesn't exists yet.
+ */
+ EventTree* QueueMutation(Accessible* aContainer);
+
+ class MoveGuard final {
+ public:
+ explicit MoveGuard(NotificationController* aController)
+ : mController(aController) {
+#ifdef DEBUG
+ MOZ_ASSERT(!mController->mMoveGuardOnStack,
+ "Move guard is on stack already!");
+ mController->mMoveGuardOnStack = true;
+#endif
+ }
+ ~MoveGuard() {
+#ifdef DEBUG
+ MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!");
+ mController->mMoveGuardOnStack = false;
+#endif
+ mController->mPrecedingEvents.Clear();
+ }
+
+ private:
+ NotificationController* mController;
+ };
+
+#ifdef A11Y_LOG
+ const EventTree& RootEventTree() const { return mEventTree; };
+#endif
+
+ /**
+ * Queue a mutation event to emit if not coalesced away. Returns true if the
+ * event was queued and has not yet been coalesced.
+ */
+ bool QueueMutationEvent(AccTreeMutationEvent* aEvent);
+
+ /**
+ * Coalesce all queued mutation events.
+ */
+ void CoalesceMutationEvents();
+
+ /**
+ * Schedule binding the child document to the tree of this document.
+ */
+ void ScheduleChildDocBinding(DocAccessible* aDocument);
+
+ /**
+ * Schedule the accessible tree update because of rendered text changes.
+ */
+ inline void ScheduleTextUpdate(nsIContent* aTextNode) {
+ // Make sure we are not called with a node that is not in the DOM tree or
+ // not visible.
+ MOZ_ASSERT(aTextNode->GetParentNode(), "A text node is not in DOM");
+ MOZ_ASSERT(aTextNode->GetPrimaryFrame(),
+ "A text node doesn't have a frame");
+ MOZ_ASSERT(aTextNode->GetPrimaryFrame()->StyleVisibility()->IsVisible(),
+ "A text node is not visible");
+
+ mTextHash.PutEntry(aTextNode);
+ ScheduleProcessing();
+ }
+
+ /**
+ * Pend accessible tree update for content insertion.
+ */
+ void ScheduleContentInsertion(Accessible* aContainer,
+ nsTArray<nsCOMPtr<nsIContent>>& aInsertions);
+
+ /**
+ * Pend an accessible subtree relocation.
+ */
+ void ScheduleRelocation(Accessible* aOwner) {
+ if (!mRelocations.Contains(aOwner)) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mRelocations.AppendElement(aOwner);
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Start to observe refresh to make notifications and events processing after
+ * layout.
+ */
+ void ScheduleProcessing();
+
+ /**
+ * Process the generic notification synchronously if there are no pending
+ * layout changes and no notifications are pending or being processed right
+ * now. Otherwise, queue it up to process asynchronously.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * the notification is processed.
+ */
+ template <class Class, class... Args>
+ inline void HandleNotification(
+ Class* aInstance,
+ typename TNotification<Class, Args...>::Callback aMethod,
+ Args*... aArgs) {
+ if (!IsUpdatePending()) {
+#ifdef A11Y_LOG
+ if (mozilla::a11y::logging::IsEnabled(
+ mozilla::a11y::logging::eNotifications))
+ mozilla::a11y::logging::Text("sync notification processing");
+#endif
+ (aInstance->*aMethod)(aArgs...);
+ return;
+ }
+
+ RefPtr<Notification> notification =
+ new TNotification<Class, Args...>(aInstance, aMethod, aArgs...);
+ if (notification) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mNotifications.AppendElement(notification);
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Schedule the generic notification to process asynchronously.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * the notification is processed.
+ */
+ template <class Class>
+ inline void ScheduleNotification(
+ Class* aInstance, typename TNotification<Class>::Callback aMethod) {
+ RefPtr<Notification> notification =
+ new TNotification<Class>(aInstance, aMethod);
+ if (notification) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mNotifications.AppendElement(notification);
+ ScheduleProcessing();
+ }
+ }
+
+ template <class Class, class Arg>
+ inline void ScheduleNotification(
+ Class* aInstance, typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg) {
+ RefPtr<Notification> notification =
+ new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+ if (notification) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ mNotifications.AppendElement(notification);
+ ScheduleProcessing();
+ }
+ }
+
+#ifdef DEBUG
+ bool IsUpdating() const {
+ return mObservingState == eRefreshProcessingForUpdate;
+ }
+#endif
+
+ protected:
+ virtual ~NotificationController();
+
+ nsCycleCollectingAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ /**
+ * Return true if the accessible tree state update is pending.
+ */
+ bool IsUpdatePending();
+
+ /**
+ * Return true if we should wait for processing from the parent before we can
+ * process our own queue.
+ */
+ bool WaitingForParent();
+
+ private:
+ NotificationController(const NotificationController&);
+ NotificationController& operator=(const NotificationController&);
+
+ // nsARefreshObserver
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ /**
+ * Set and returns a hide event, paired with a show event, for the move.
+ */
+ void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs) {
+ if (mPrecedingEvents.Length() > 0) {
+ aEvs->AppendElements(std::move(mPrecedingEvents));
+ }
+ }
+ void StorePrecedingEvent(AccHideEvent* aEv) {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.AppendElement(aEv);
+ }
+ void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs) {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.InsertElementsAt(0, aEvs);
+ }
+
+ private:
+ /**
+ * get rid of a mutation event that is no longer necessary.
+ */
+ void DropMutationEvent(AccTreeMutationEvent* aEvent);
+
+ /**
+ * Fire all necessary mutation events.
+ */
+ void ProcessMutationEvents();
+
+ /**
+ * Indicates whether we're waiting on an event queue processing from our
+ * notification controller to flush events.
+ */
+ enum eObservingState {
+ eNotObservingRefresh,
+ eRefreshObserving,
+ eRefreshProcessing,
+ eRefreshProcessingForUpdate
+ };
+ eObservingState mObservingState;
+
+ /**
+ * The presshell of the document accessible.
+ */
+ PresShell* mPresShell;
+
+ /**
+ * Child documents that needs to be bound to the tree.
+ */
+ nsTArray<RefPtr<DocAccessible>> mHangingChildDocuments;
+
+ /**
+ * Pending accessible tree update notifications for content insertions.
+ */
+ nsClassHashtable<nsRefPtrHashKey<Accessible>, nsTArray<nsCOMPtr<nsIContent>>>
+ mContentInsertions;
+
+ template <class T>
+ class nsCOMPtrHashKey : public PLDHashEntryHdr {
+ public:
+ typedef T* KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
+ nsCOMPtrHashKey(nsCOMPtrHashKey<T>&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {}
+ ~nsCOMPtrHashKey() {}
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return NS_PTR_TO_INT32(aKey) >> 2;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ nsCOMPtr<T> mKey;
+ };
+
+ /**
+ * Pending accessible tree update notifications for rendered text changes.
+ */
+ nsTHashtable<nsCOMPtrHashKey<nsIContent>> mTextHash;
+
+ /**
+ * Other notifications like DOM events. Don't make this an AutoTArray; we
+ * use SwapElements() on it.
+ */
+ nsTArray<RefPtr<Notification>> mNotifications;
+
+ /**
+ * Holds all scheduled relocations.
+ */
+ nsTArray<RefPtr<Accessible>> mRelocations;
+
+ /**
+ * Holds all mutation events.
+ */
+ EventTree mEventTree;
+
+ /**
+ * A temporary collection of hide events that should be fired before related
+ * show event. Used by EventTree.
+ */
+ nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
+
+#ifdef DEBUG
+ bool mMoveGuardOnStack;
+#endif
+
+ friend class MoveGuard;
+ friend class EventTree;
+
+ /**
+ * A list of all mutation events we may want to emit. Ordered from the first
+ * event that should be emitted to the last one to emit.
+ */
+ RefPtr<AccTreeMutationEvent> mFirstMutationEvent;
+ RefPtr<AccTreeMutationEvent> mLastMutationEvent;
+
+ /**
+ * A class to map an accessible and event type to an event.
+ */
+ class EventMap {
+ public:
+ enum EventType {
+ ShowEvent = 0x0,
+ HideEvent = 0x1,
+ ReorderEvent = 0x2,
+ };
+
+ void PutEvent(AccTreeMutationEvent* aEvent);
+ AccTreeMutationEvent* GetEvent(Accessible* aTarget, EventType aType);
+ void RemoveEvent(AccTreeMutationEvent* aEvent);
+ void Clear() { mTable.Clear(); }
+
+ private:
+ EventType GetEventType(AccTreeMutationEvent* aEvent);
+
+ nsRefPtrHashtable<nsUint64HashKey, AccTreeMutationEvent> mTable;
+ };
+
+ EventMap mMutationMap;
+ uint32_t mEventGeneration;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_NotificationController_h_
diff --git a/accessible/base/Pivot.cpp b/accessible/base/Pivot.cpp
new file mode 100644
index 0000000000..f654fb9333
--- /dev/null
+++ b/accessible/base/Pivot.cpp
@@ -0,0 +1,596 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Pivot.h"
+
+#include "AccIterator.h"
+#include "Accessible.h"
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Pivot
+////////////////////////////////////////////////////////////////////////////////
+
+Pivot::Pivot(const AccessibleOrProxy& aRoot) : mRoot(aRoot) {
+ MOZ_COUNT_CTOR(Pivot);
+}
+
+Pivot::~Pivot() { MOZ_COUNT_DTOR(Pivot); }
+
+AccessibleOrProxy Pivot::AdjustStartPosition(AccessibleOrProxy& aAnchor,
+ PivotRule& aRule,
+ uint16_t* aFilterResult) {
+ AccessibleOrProxy matched = aAnchor;
+ *aFilterResult = aRule.Match(aAnchor);
+
+ if (aAnchor != mRoot) {
+ for (AccessibleOrProxy temp = aAnchor.Parent();
+ !temp.IsNull() && temp != mRoot; temp = temp.Parent()) {
+ uint16_t filtered = aRule.Match(temp);
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
+ *aFilterResult = filtered;
+ matched = temp;
+ }
+ }
+ }
+
+ return matched;
+}
+
+AccessibleOrProxy Pivot::SearchBackward(AccessibleOrProxy& aAnchor,
+ PivotRule& aRule, bool aSearchCurrent) {
+ // Initial position could be unset, in that case return null AoP.
+ if (aAnchor.IsNull()) {
+ return aAnchor;
+ }
+
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ AccessibleOrProxy accOrProxy = AdjustStartPosition(aAnchor, aRule, &filtered);
+
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ return accOrProxy;
+ }
+
+ while (accOrProxy != mRoot) {
+ AccessibleOrProxy parent = accOrProxy.Parent();
+ int32_t idxInParent = accOrProxy.IndexInParent();
+ while (idxInParent > 0) {
+ accOrProxy = parent.ChildAt(--idxInParent);
+ if (accOrProxy.IsNull()) {
+ continue;
+ }
+
+ filtered = aRule.Match(accOrProxy);
+
+ AccessibleOrProxy lastChild = accOrProxy.LastChild();
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ !lastChild.IsNull()) {
+ parent = accOrProxy;
+ accOrProxy = lastChild;
+ idxInParent = accOrProxy.IndexInParent();
+ filtered = aRule.Match(accOrProxy);
+ lastChild = accOrProxy.LastChild();
+ }
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ return accOrProxy;
+ }
+ }
+
+ accOrProxy = parent;
+ if (accOrProxy.IsNull()) {
+ break;
+ }
+
+ filtered = aRule.Match(accOrProxy);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ return accOrProxy;
+ }
+ }
+
+ return AccessibleOrProxy();
+}
+
+AccessibleOrProxy Pivot::SearchForward(AccessibleOrProxy& aAnchor,
+ PivotRule& aRule, bool aSearchCurrent) {
+ // Initial position could be not set, in that case begin search from root.
+ AccessibleOrProxy accOrProxy = !aAnchor.IsNull() ? aAnchor : mRoot;
+
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ accOrProxy = AdjustStartPosition(accOrProxy, aRule, &filtered);
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ return accOrProxy;
+ }
+
+ while (true) {
+ AccessibleOrProxy firstChild = accOrProxy.FirstChild();
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ !firstChild.IsNull()) {
+ accOrProxy = firstChild;
+ filtered = aRule.Match(accOrProxy);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ return accOrProxy;
+ }
+ firstChild = accOrProxy.FirstChild();
+ }
+
+ AccessibleOrProxy sibling = AccessibleOrProxy();
+ AccessibleOrProxy temp = accOrProxy;
+ do {
+ if (temp == mRoot) {
+ break;
+ }
+
+ sibling = temp.NextSibling();
+
+ if (!sibling.IsNull()) {
+ break;
+ }
+ temp = temp.Parent();
+ } while (!temp.IsNull());
+
+ if (sibling.IsNull()) {
+ break;
+ }
+
+ accOrProxy = sibling;
+ filtered = aRule.Match(accOrProxy);
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ return accOrProxy;
+ }
+ }
+
+ return AccessibleOrProxy();
+}
+
+// TODO: This method does not work for proxy accessibles
+HyperTextAccessible* Pivot::SearchForText(Accessible* aAnchor, bool aBackward) {
+ if (!mRoot.IsAccessible()) {
+ return nullptr;
+ }
+ Accessible* accessible = aAnchor;
+ while (true) {
+ Accessible* child = nullptr;
+
+ while ((child = (aBackward ? accessible->LastChild()
+ : accessible->FirstChild()))) {
+ accessible = child;
+ if (child->IsHyperText()) {
+ return child->AsHyperText();
+ }
+ }
+
+ Accessible* sibling = nullptr;
+ Accessible* temp = accessible;
+ do {
+ if (temp == mRoot.AsAccessible()) {
+ break;
+ }
+
+ // Unlike traditional pre-order traversal we revisit the parent
+ // nodes when we go up the tree. This is because our starting point
+ // may be a subtree or a leaf. If it's parent matches, it should
+ // take precedent over a sibling.
+ if (temp != aAnchor && temp->IsHyperText()) {
+ return temp->AsHyperText();
+ }
+
+ if (sibling) {
+ break;
+ }
+
+ sibling = aBackward ? temp->PrevSibling() : temp->NextSibling();
+ } while ((temp = temp->Parent()));
+
+ if (!sibling) {
+ break;
+ }
+
+ accessible = sibling;
+ if (accessible->IsHyperText()) {
+ return accessible->AsHyperText();
+ }
+ }
+
+ return nullptr;
+}
+
+AccessibleOrProxy Pivot::Next(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aIncludeStart) {
+ return SearchForward(aAnchor, aRule, aIncludeStart);
+}
+
+AccessibleOrProxy Pivot::Prev(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aIncludeStart) {
+ return SearchBackward(aAnchor, aRule, aIncludeStart);
+}
+
+AccessibleOrProxy Pivot::First(PivotRule& aRule) {
+ return SearchForward(mRoot, aRule, true);
+}
+
+AccessibleOrProxy Pivot::Last(PivotRule& aRule) {
+ AccessibleOrProxy lastAccOrProxy = mRoot;
+
+ // First go to the last accessible in pre-order
+ while (lastAccOrProxy.HasChildren()) {
+ lastAccOrProxy = lastAccOrProxy.LastChild();
+ }
+
+ // Search backwards from last accessible and find the last occurrence in the
+ // doc
+ return SearchBackward(lastAccOrProxy, aRule, true);
+}
+
+// TODO: This method does not work for proxy accessibles
+Accessible* Pivot::NextText(Accessible* aAnchor, int32_t* aStartOffset,
+ int32_t* aEndOffset, int32_t aBoundaryType) {
+ if (!mRoot.IsAccessible()) {
+ return nullptr;
+ }
+
+ int32_t tempStart = *aStartOffset, tempEnd = *aEndOffset;
+ Accessible* tempPosition = aAnchor;
+
+ // if we're starting on a text leaf, translate the offsets to the
+ // HyperTextAccessible parent and start from there.
+ if (aAnchor->IsTextLeaf() && aAnchor->Parent() &&
+ aAnchor->Parent()->IsHyperText()) {
+ HyperTextAccessible* text = aAnchor->Parent()->AsHyperText();
+ tempPosition = text;
+ int32_t childOffset = text->GetChildOffset(aAnchor);
+ if (tempEnd == -1) {
+ tempStart = 0;
+ tempEnd = 0;
+ }
+ tempStart += childOffset;
+ tempEnd += childOffset;
+ }
+
+ while (true) {
+ MOZ_ASSERT(tempPosition);
+ Accessible* curPosition = tempPosition;
+ HyperTextAccessible* text = nullptr;
+ // Find the nearest text node using a preorder traversal starting from
+ // the current node.
+ if (!(text = tempPosition->AsHyperText())) {
+ text = SearchForText(tempPosition, false);
+ if (!text) {
+ return nullptr;
+ }
+
+ if (text != curPosition) {
+ tempStart = tempEnd = -1;
+ }
+ tempPosition = text;
+ }
+
+ // If the search led to the parent of the node we started on (e.g. when
+ // starting on a text leaf), start the text movement from the end of that
+ // node, otherwise we just default to 0.
+ if (tempEnd == -1) {
+ tempEnd =
+ text == curPosition->Parent() ? text->GetChildOffset(curPosition) : 0;
+ }
+
+ // If there's no more text on the current node, try to find the next text
+ // node; if there isn't one, bail out.
+ if (tempEnd == static_cast<int32_t>(text->CharacterCount())) {
+ if (tempPosition == mRoot.AsAccessible()) {
+ return nullptr;
+ }
+
+ // If we're currently sitting on a link, try move to either the next
+ // sibling or the parent, whichever is closer to the current end
+ // offset. Otherwise, do a forward search for the next node to land on
+ // (we don't do this in the first case because we don't want to go to the
+ // subtree).
+ Accessible* sibling = tempPosition->NextSibling();
+ if (tempPosition->IsLink()) {
+ if (sibling && sibling->IsLink()) {
+ tempStart = tempEnd = -1;
+ tempPosition = sibling;
+ } else {
+ tempStart = tempPosition->StartOffset();
+ tempEnd = tempPosition->EndOffset();
+ tempPosition = tempPosition->Parent();
+ }
+ } else {
+ tempPosition = SearchForText(tempPosition, false);
+ if (!tempPosition) {
+ return nullptr;
+ }
+
+ tempStart = tempEnd = -1;
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundaryType) {
+ case nsIAccessiblePivot::CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case nsIAccessiblePivot::WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ case nsIAccessiblePivot::LINE_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_LINE_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_LINE_END;
+ break;
+ default:
+ return nullptr;
+ }
+
+ nsAutoString unusedText;
+ int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd;
+ text->TextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText);
+ text->TextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd,
+ unusedText);
+ int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd;
+ tempStart = potentialStart > tempStart ? potentialStart : currentEnd;
+
+ // The offset range we've obtained might have embedded characters in it,
+ // limit the range to the start of the first occurrence of an embedded
+ // character.
+ Accessible* childAtOffset = nullptr;
+ for (int32_t i = tempStart; i < tempEnd; i++) {
+ childAtOffset = text->GetChildAtOffset(i);
+ if (childAtOffset && childAtOffset->IsHyperText()) {
+ tempEnd = i;
+ break;
+ }
+ }
+ // If there's an embedded character at the very start of the range, we
+ // instead want to traverse into it. So restart the movement with
+ // the child as the starting point.
+ if (childAtOffset && childAtOffset->IsHyperText() &&
+ tempStart == static_cast<int32_t>(childAtOffset->StartOffset())) {
+ tempPosition = childAtOffset;
+ tempStart = tempEnd = -1;
+ continue;
+ }
+
+ *aStartOffset = tempStart;
+ *aEndOffset = tempEnd;
+
+ MOZ_ASSERT(tempPosition);
+ return tempPosition;
+ }
+}
+
+// TODO: This method does not work for proxy accessibles
+Accessible* Pivot::PrevText(Accessible* aAnchor, int32_t* aStartOffset,
+ int32_t* aEndOffset, int32_t aBoundaryType) {
+ if (!mRoot.IsAccessible()) {
+ return nullptr;
+ }
+
+ int32_t tempStart = *aStartOffset, tempEnd = *aEndOffset;
+ Accessible* tempPosition = aAnchor;
+
+ // if we're starting on a text leaf, translate the offsets to the
+ // HyperTextAccessible parent and start from there.
+ if (aAnchor->IsTextLeaf() && aAnchor->Parent() &&
+ aAnchor->Parent()->IsHyperText()) {
+ HyperTextAccessible* text = aAnchor->Parent()->AsHyperText();
+ tempPosition = text;
+ int32_t childOffset = text->GetChildOffset(aAnchor);
+ if (tempStart == -1) {
+ tempStart = nsAccUtils::TextLength(aAnchor);
+ tempEnd = tempStart;
+ }
+ tempStart += childOffset;
+ tempEnd += childOffset;
+ }
+
+ while (true) {
+ MOZ_ASSERT(tempPosition);
+
+ Accessible* curPosition = tempPosition;
+ HyperTextAccessible* text;
+ // Find the nearest text node using a reverse preorder traversal starting
+ // from the current node.
+ if (!(text = tempPosition->AsHyperText())) {
+ text = SearchForText(tempPosition, true);
+ if (!text) {
+ return nullptr;
+ }
+
+ if (text != curPosition) {
+ tempStart = tempEnd = -1;
+ }
+ tempPosition = text;
+ }
+
+ // If the search led to the parent of the node we started on (e.g. when
+ // starting on a text leaf), start the text movement from the end offset
+ // of that node. Otherwise we just default to the last offset in the parent.
+ if (tempStart == -1) {
+ if (tempPosition != curPosition && text == curPosition->Parent()) {
+ tempStart = text->GetChildOffset(curPosition) +
+ nsAccUtils::TextLength(curPosition);
+ } else {
+ tempStart = text->CharacterCount();
+ }
+ }
+
+ // If there's no more text on the current node, try to find the previous
+ // text node; if there isn't one, bail out.
+ if (tempStart == 0) {
+ if (tempPosition == mRoot.AsAccessible()) {
+ return nullptr;
+ }
+
+ // If we're currently sitting on a link, try move to either the previous
+ // sibling or the parent, whichever is closer to the current end
+ // offset. Otherwise, do a forward search for the next node to land on
+ // (we don't do this in the first case because we don't want to go to the
+ // subtree).
+ Accessible* sibling = tempPosition->PrevSibling();
+ if (tempPosition->IsLink()) {
+ if (sibling && sibling->IsLink()) {
+ HyperTextAccessible* siblingText = sibling->AsHyperText();
+ tempStart = tempEnd =
+ siblingText ? siblingText->CharacterCount() : -1;
+ tempPosition = sibling;
+ } else {
+ tempStart = tempPosition->StartOffset();
+ tempEnd = tempPosition->EndOffset();
+ tempPosition = tempPosition->Parent();
+ }
+ } else {
+ HyperTextAccessible* tempText = SearchForText(tempPosition, true);
+ if (!tempText) {
+ return nullptr;
+ }
+
+ tempPosition = tempText;
+ tempStart = tempEnd = tempText->CharacterCount();
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundaryType) {
+ case nsIAccessiblePivot::CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case nsIAccessiblePivot::WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ case nsIAccessiblePivot::LINE_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_LINE_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_LINE_END;
+ break;
+ default:
+ return nullptr;
+ }
+
+ nsAutoString unusedText;
+ int32_t newStart = 0, newEnd = 0, currentStart = tempStart,
+ potentialEnd = 0;
+ text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd,
+ unusedText);
+ if (newStart < tempStart) {
+ tempStart = newEnd >= currentStart ? newStart : newEnd;
+ } else {
+ // XXX: In certain odd cases newStart is equal to tempStart
+ text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart,
+ &tempStart, unusedText);
+ }
+ text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd,
+ unusedText);
+ tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart;
+
+ // The offset range we've obtained might have embedded characters in it,
+ // limit the range to the start of the last occurrence of an embedded
+ // character.
+ Accessible* childAtOffset = nullptr;
+ for (int32_t i = tempEnd - 1; i >= tempStart; i--) {
+ childAtOffset = text->GetChildAtOffset(i);
+ if (childAtOffset && !childAtOffset->IsText()) {
+ tempStart = childAtOffset->EndOffset();
+ break;
+ }
+ }
+ // If there's an embedded character at the very end of the range, we
+ // instead want to traverse into it. So restart the movement with
+ // the child as the starting point.
+ if (childAtOffset && !childAtOffset->IsText() &&
+ tempEnd == static_cast<int32_t>(childAtOffset->EndOffset())) {
+ tempPosition = childAtOffset;
+ tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount();
+ continue;
+ }
+
+ *aStartOffset = tempStart;
+ *aEndOffset = tempEnd;
+
+ MOZ_ASSERT(tempPosition);
+ return tempPosition;
+ }
+}
+
+AccessibleOrProxy Pivot::AtPoint(int32_t aX, int32_t aY, PivotRule& aRule) {
+ AccessibleOrProxy match = AccessibleOrProxy();
+ AccessibleOrProxy child =
+ mRoot.ChildAtPoint(aX, aY, Accessible::eDeepestChild);
+ while (!child.IsNull() && (mRoot != child)) {
+ uint16_t filtered = aRule.Match(child);
+
+ // Ignore any matching nodes that were below this one
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
+ match = AccessibleOrProxy();
+ }
+
+ // Match if no node below this is a match
+ if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) &&
+ match.IsNull()) {
+ nsIntRect childRect = child.IsAccessible()
+ ? child.AsAccessible()->Bounds()
+ : child.AsProxy()->Bounds();
+ // Double-check child's bounds since the deepest child may have been out
+ // of bounds. This assures we don't return a false positive.
+ if (childRect.Contains(aX, aY)) {
+ match = child;
+ }
+ }
+
+ child = child.Parent();
+ }
+
+ return match;
+}
+
+// Role Rule
+
+PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole)
+ : mRole(aRole), mDirectDescendantsFrom(nullptr) {}
+
+PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole,
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : mRole(aRole), mDirectDescendantsFrom(aDirectDescendantsFrom) {}
+
+uint16_t PivotRoleRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (nsAccUtils::MustPrune(aAccOrProxy)) {
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ if (!mDirectDescendantsFrom.IsNull() &&
+ (aAccOrProxy != mDirectDescendantsFrom)) {
+ // If we've specified mDirectDescendantsFrom, we should ignore
+ // non-direct descendants of from the specified AoP. Because
+ // pivot performs a preorder traversal, the first aAccOrProxy
+ // object(s) that don't equal mDirectDescendantsFrom will be
+ // mDirectDescendantsFrom's children. We'll process them, but ignore
+ // their subtrees thereby processing direct descendants of
+ // mDirectDescendantsFrom only.
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ if (aAccOrProxy.Role() == mRole) {
+ result |= nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return result;
+}
diff --git a/accessible/base/Pivot.h b/accessible/base/Pivot.h
new file mode 100644
index 0000000000..e4b5b1c058
--- /dev/null
+++ b/accessible/base/Pivot.h
@@ -0,0 +1,108 @@
+/* -*- 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 mozilla_a11y_Pivot_h_
+#define mozilla_a11y_Pivot_h_
+
+#include <stdint.h>
+#include "Role.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "AccessibleOrProxy.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+class DocAccessible;
+
+class PivotRule {
+ public:
+ // A filtering function that returns a bitmask from
+ // nsIAccessibleTraversalRule: FILTER_IGNORE (0x0): Don't match this
+ // accessible. FILTER_MATCH (0x1): Match this accessible FILTER_IGNORE_SUBTREE
+ // (0x2): Ignore accessible's subtree.
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) = 0;
+};
+
+// The Pivot class is used for searching for accessible nodes in a given subtree
+// with a given criteria. Since it only holds a weak reference to the root,
+// this class is meant to be used primarily on the stack.
+class Pivot final {
+ public:
+ explicit Pivot(const AccessibleOrProxy& aRoot);
+ Pivot() = delete;
+ Pivot(const Pivot&) = delete;
+ Pivot& operator=(const Pivot&) = delete;
+
+ ~Pivot();
+
+ // Return the next accessible after aAnchor in pre-order that matches the
+ // given rule. If aIncludeStart, return aAnchor if it matches the rule.
+ AccessibleOrProxy Next(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aIncludeStart = false);
+
+ // Return the previous accessible before aAnchor in pre-order that matches the
+ // given rule. If aIncludeStart, return aAnchor if it matches the rule.
+ AccessibleOrProxy Prev(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aIncludeStart = false);
+
+ // Return the first accessible within the root that matches the pivot rule.
+ AccessibleOrProxy First(PivotRule& aRule);
+
+ // Return the last accessible within the root that matches the pivot rule.
+ AccessibleOrProxy Last(PivotRule& aRule);
+
+ // Return the next range of text according to the boundary type.
+ Accessible* NextText(Accessible* aAnchor, int32_t* aStartOffset,
+ int32_t* aEndOffset, int32_t aBoundaryType);
+
+ // Return the previous range of text according to the boundary type.
+ Accessible* PrevText(Accessible* aAnchor, int32_t* aStartOffset,
+ int32_t* aEndOffset, int32_t aBoundaryType);
+
+ // Return the accessible at the given screen coordinate if it matches the
+ // pivot rule.
+ AccessibleOrProxy AtPoint(int32_t aX, int32_t aY, PivotRule& aRule);
+
+ private:
+ AccessibleOrProxy AdjustStartPosition(AccessibleOrProxy& aAnchor,
+ PivotRule& aRule,
+ uint16_t* aFilterResult);
+
+ // Search in preorder for the first accessible to match the rule.
+ AccessibleOrProxy SearchForward(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aSearchCurrent);
+
+ // Reverse search in preorder for the first accessible to match the rule.
+ AccessibleOrProxy SearchBackward(AccessibleOrProxy& aAnchor, PivotRule& aRule,
+ bool aSearchCurrent);
+
+ // Search in preorder for the first text accessible.
+ HyperTextAccessible* SearchForText(Accessible* aAnchor, bool aBackward);
+
+ AccessibleOrProxy mRoot;
+};
+
+/**
+ * This rule matches accessibles on a given role, filtering out non-direct
+ * descendants if necessary.
+ */
+class PivotRoleRule : public PivotRule {
+ public:
+ explicit PivotRoleRule(role aRole);
+ explicit PivotRoleRule(role aRole, AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ protected:
+ role mRole;
+ AccessibleOrProxy mDirectDescendantsFrom;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_Pivot_h_
diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h
new file mode 100644
index 0000000000..d2fefea68b
--- /dev/null
+++ b/accessible/base/Platform.h
@@ -0,0 +1,160 @@
+/* -*- 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_Platform_h
+#define mozilla_a11y_Platform_h
+
+#include <stdint.h>
+#include "nsStringFwd.h"
+
+#if defined(ANDROID)
+# include "nsTArray.h"
+# include "nsRect.h"
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+# include "mozilla/a11y/Role.h"
+#endif
+
+#if defined(XP_WIN)
+# include "Units.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class ProxyAccessible;
+
+enum EPlatformDisabledState {
+ ePlatformIsForceEnabled = -1,
+ ePlatformIsEnabled = 0,
+ ePlatformIsDisabled = 1
+};
+
+/**
+ * Return the platform disabled state.
+ */
+EPlatformDisabledState PlatformDisabledState();
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+/**
+ * Perform initialization that should be done as soon as possible, in order
+ * to minimize startup time.
+ * XXX: this function and the next defined in ApplicationAccessibleWrap.cpp
+ */
+void PreInit();
+#endif
+
+#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_MACOSX)
+/**
+ * Is platform accessibility enabled.
+ * Only used on linux with atk and MacOS for now.
+ */
+bool ShouldA11yBeEnabled();
+#endif
+
+#if defined(XP_WIN)
+/*
+ * Do we have AccessibleHandler.dll registered.
+ */
+bool IsHandlerRegistered();
+
+/*
+ * Name of platform service that instantiated accessibility
+ */
+void SetInstantiator(const uint32_t aInstantiatorPid);
+bool GetInstantiator(nsIFile** aOutInstantiator);
+#endif
+
+/**
+ * Called to initialize platform specific accessibility support.
+ * Note this is called after internal accessibility support is initialized.
+ */
+void PlatformInit();
+
+/**
+ * Shutdown platform accessibility.
+ * Note this is called before internal accessibility support is shutdown.
+ */
+void PlatformShutdown();
+
+/**
+ * called when a new ProxyAccessible is created, so the platform may setup a
+ * wrapper for it, or take other action.
+ */
+void ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces);
+
+/**
+ * Called just before a ProxyAccessible is destroyed so its wrapper can be
+ * disposed of and other action taken.
+ */
+void ProxyDestroyed(ProxyAccessible*);
+
+/**
+ * Callied when an event is fired on a proxied accessible.
+ */
+void ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType);
+void ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
+ bool aEnabled);
+
+#if defined(XP_WIN)
+void ProxyFocusEvent(ProxyAccessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect);
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect);
+#else
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset,
+ bool aIsSelectionCollapsed);
+#endif
+void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aIsInsert,
+ bool aFromUser);
+void ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
+ bool aInsert, bool aFromUser);
+void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
+ uint32_t aType);
+
+#if defined(ANDROID)
+MOZ_CAN_RUN_SCRIPT
+void ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
+ ProxyAccessible* aOldPosition,
+ int32_t aOldStartOffset,
+ int32_t aOldEndOffset,
+ ProxyAccessible* aNewPosition,
+ int32_t aNewStartOffset,
+ int32_t aNewEndOffset, int16_t aReason,
+ int16_t aBoundaryType, bool aFromUser);
+
+void ProxyScrollingEvent(ProxyAccessible* aTarget, uint32_t aEventType,
+ uint32_t aScrollX, uint32_t aScrollY,
+ uint32_t aMaxScrollX, uint32_t aMaxScrollY);
+
+void ProxyAnnouncementEvent(ProxyAccessible* aTarget,
+ const nsString& aAnnouncement, uint16_t aPriority);
+
+class BatchData;
+
+void ProxyBatch(ProxyAccessible* aDocument, const uint64_t aBatchType,
+ const nsTArray<ProxyAccessible*>& aAccessibles,
+ const nsTArray<BatchData>& aData);
+
+bool LocalizeString(
+ const char* aToken, nsAString& aLocalized,
+ const nsTArray<nsString>& aFormatString = nsTArray<nsString>());
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+class TextRangeData;
+void ProxyTextSelectionChangeEvent(ProxyAccessible* aTarget,
+ const nsTArray<TextRangeData>& aSelection);
+
+void ProxyRoleChangedEvent(ProxyAccessible* aTarget, const a11y::role& aRole);
+#endif
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_Platform_h
diff --git a/accessible/base/Relation.h b/accessible/base/Relation.h
new file mode 100644
index 0000000000..7b7f521eaf
--- /dev/null
+++ b/accessible/base/Relation.h
@@ -0,0 +1,98 @@
+/* -*- 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_relation_h_
+#define mozilla_a11y_relation_h_
+
+#include "AccIterator.h"
+
+#include <memory>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * A collection of relation targets of a certain type. Targets are computed
+ * lazily while enumerating.
+ */
+class Relation {
+ public:
+ Relation() : mFirstIter(nullptr), mLastIter(nullptr) {}
+
+ explicit Relation(AccIterable* aIter) : mFirstIter(aIter), mLastIter(aIter) {}
+
+ explicit Relation(Accessible* aAcc)
+ : mFirstIter(nullptr), mLastIter(nullptr) {
+ AppendTarget(aAcc);
+ }
+
+ Relation(DocAccessible* aDocument, nsIContent* aContent)
+ : mFirstIter(nullptr), mLastIter(nullptr) {
+ AppendTarget(aDocument, aContent);
+ }
+
+ Relation(Relation&& aOther)
+ : mFirstIter(std::move(aOther.mFirstIter)), mLastIter(aOther.mLastIter) {
+ aOther.mLastIter = nullptr;
+ }
+
+ Relation& operator=(Relation&& aRH) {
+ mFirstIter = std::move(aRH.mFirstIter);
+ mLastIter = aRH.mLastIter;
+ aRH.mLastIter = nullptr;
+ return *this;
+ }
+
+ inline void AppendIter(AccIterable* aIter) {
+ if (mLastIter)
+ mLastIter->mNextIter.reset(aIter);
+ else
+ mFirstIter.reset(aIter);
+
+ mLastIter = aIter;
+ }
+
+ /**
+ * Append the given accessible to the set of related accessibles.
+ */
+ inline void AppendTarget(Accessible* aAcc) {
+ if (aAcc) AppendIter(new SingleAccIterator(aAcc));
+ }
+
+ /**
+ * Append the one accessible for this content node to the set of related
+ * accessibles.
+ */
+ void AppendTarget(DocAccessible* aDocument, nsIContent* aContent) {
+ if (aContent) AppendTarget(aDocument->GetAccessible(aContent));
+ }
+
+ /**
+ * compute and return the next related accessible.
+ */
+ inline Accessible* Next() {
+ Accessible* target = nullptr;
+
+ while (mFirstIter && !(target = mFirstIter->Next()))
+ mFirstIter = std::move(mFirstIter->mNextIter);
+
+ if (!mFirstIter) mLastIter = nullptr;
+
+ return target;
+ }
+
+ private:
+ Relation& operator=(const Relation&) = delete;
+ Relation(const Relation&) = delete;
+
+ std::unique_ptr<AccIterable> mFirstIter;
+ AccIterable* mLastIter;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/RelationType.h b/accessible/base/RelationType.h
new file mode 100644
index 0000000000..e59cf1f7b8
--- /dev/null
+++ b/accessible/base/RelationType.h
@@ -0,0 +1,163 @@
+/* -*- 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_relationtype_h_
+#define mozilla_a11y_relationtype_h_
+
+namespace mozilla {
+namespace a11y {
+
+enum class RelationType {
+
+ /**
+ * This object is labelled by a target object.
+ */
+ LABELLED_BY = 0x00,
+
+ /**
+ * This object is label for a target object.
+ */
+ LABEL_FOR = 0x01,
+
+ /**
+ * This object is described by the target object.
+ */
+ DESCRIBED_BY = 0x02,
+
+ /**
+ * This object is describes the target object.
+ */
+ DESCRIPTION_FOR = 0x3,
+
+ /**
+ * This object is a child of a target object.
+ */
+ NODE_CHILD_OF = 0x4,
+
+ /**
+ * This object is a parent of a target object. A dual relation to
+ * NODE_CHILD_OF.
+ */
+ NODE_PARENT_OF = 0x5,
+
+ /**
+ * Some attribute of this object is affected by a target object.
+ */
+ CONTROLLED_BY = 0x06,
+
+ /**
+ * This object is interactive and controls some attribute of a target object.
+ */
+ CONTROLLER_FOR = 0x07,
+
+ /**
+ * Content flows from this object to a target object, i.e. has content that
+ * flows logically to another object in a sequential way, e.g. text flow.
+ */
+ FLOWS_TO = 0x08,
+
+ /**
+ * Content flows to this object from a target object, i.e. has content that
+ * flows logically from another object in a sequential way, e.g. text flow.
+ */
+ FLOWS_FROM = 0x09,
+
+ /**
+ * This object is a member of a group of one or more objects. When there is
+ * more than one object in the group each member may have one and the same
+ * target, e.g. a grouping object. It is also possible that each member has
+ * multiple additional targets, e.g. one for every other member in the group.
+ */
+ MEMBER_OF = 0x0a,
+
+ /**
+ * This object is a sub window of a target object.
+ */
+ SUBWINDOW_OF = 0x0b,
+
+ /**
+ * This object embeds a target object. This relation can be used on the
+ * OBJID_CLIENT accessible for a top level window to show where the content
+ * areas are.
+ */
+ EMBEDS = 0x0c,
+
+ /**
+ * This object is embedded by a target object.
+ */
+ EMBEDDED_BY = 0x0d,
+
+ /**
+ * This object is a transient component related to the target object. When
+ * this object is activated the target object doesn't lose focus.
+ */
+ POPUP_FOR = 0x0e,
+
+ /**
+ * This object is a parent window of the target object.
+ */
+ PARENT_WINDOW_OF = 0x0f,
+
+ /**
+ * Part of a form/dialog with a related default button. It is used for
+ * MSAA/XPCOM, it isn't for IA2 or ATK.
+ */
+ DEFAULT_BUTTON = 0x10,
+
+ /**
+ * The target object is the containing document object.
+ */
+ CONTAINING_DOCUMENT = 0x11,
+
+ /**
+ * The target object is the topmost containing document object in the tab
+ * pane.
+ */
+ CONTAINING_TAB_PANE = 0x12,
+
+ /**
+ * The target object is the containing window object.
+ */
+ CONTAINING_WINDOW = 0x13,
+
+ /**
+ * The target object is the containing application object.
+ */
+ CONTAINING_APPLICATION = 0x14,
+
+ /**
+ * The target object provides the detailed, extended description for this
+ * object. It provides more detailed information than would normally be
+ * provided using the DESCRIBED_BY relation. A common use for this relation is
+ * in digital publishing where an extended description needs to be conveyed in
+ * a book that requires structural markup or the embedding of other technology
+ * to provide illustrative content.
+ */
+ DETAILS = 0x15,
+
+ /**
+ * This object provides the detailed, extended description for the target
+ * object. See DETAILS relation.
+ */
+ DETAILS_FOR = 0x16,
+
+ /**
+ * The target object is the error message for this object.
+ */
+ ERRORMSG = 0x17,
+
+ /**
+ * This object is the error message for the target object.
+ */
+ ERRORMSG_FOR = 0x18,
+
+ LAST = ERRORMSG_FOR
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/RelationTypeMap.h b/accessible/base/RelationTypeMap.h
new file mode 100644
index 0000000000..eecb184ed8
--- /dev/null
+++ b/accessible/base/RelationTypeMap.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+/**
+ * Usage: declare the macro RELATIONTYPE()with the following arguments:
+ * RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type)
+ */
+
+RELATIONTYPE(LABELLED_BY, "labelled by", ATK_RELATION_LABELLED_BY,
+ NAVRELATION_LABELLED_BY, IA2_RELATION_LABELLED_BY)
+
+RELATIONTYPE(LABEL_FOR, "label for", ATK_RELATION_LABEL_FOR,
+ NAVRELATION_LABEL_FOR, IA2_RELATION_LABEL_FOR)
+
+RELATIONTYPE(DESCRIBED_BY, "described by", ATK_RELATION_DESCRIBED_BY,
+ NAVRELATION_DESCRIBED_BY, IA2_RELATION_DESCRIBED_BY)
+
+RELATIONTYPE(DESCRIPTION_FOR, "description for", ATK_RELATION_DESCRIPTION_FOR,
+ NAVRELATION_DESCRIPTION_FOR, IA2_RELATION_DESCRIPTION_FOR)
+
+RELATIONTYPE(NODE_CHILD_OF, "node child of", ATK_RELATION_NODE_CHILD_OF,
+ NAVRELATION_NODE_CHILD_OF, IA2_RELATION_NODE_CHILD_OF)
+
+RELATIONTYPE(NODE_PARENT_OF, "node parent of", ATK_RELATION_NODE_PARENT_OF,
+ NAVRELATION_NODE_PARENT_OF, IA2_RELATION_NODE_PARENT_OF)
+
+RELATIONTYPE(CONTROLLED_BY, "controlled by", ATK_RELATION_CONTROLLED_BY,
+ NAVRELATION_CONTROLLED_BY, IA2_RELATION_CONTROLLED_BY)
+
+RELATIONTYPE(CONTROLLER_FOR, "controller for", ATK_RELATION_CONTROLLER_FOR,
+ NAVRELATION_CONTROLLER_FOR, IA2_RELATION_CONTROLLER_FOR)
+
+RELATIONTYPE(FLOWS_TO, "flows to", ATK_RELATION_FLOWS_TO, NAVRELATION_FLOWS_TO,
+ IA2_RELATION_FLOWS_TO)
+
+RELATIONTYPE(FLOWS_FROM, "flows from", ATK_RELATION_FLOWS_FROM,
+ NAVRELATION_FLOWS_FROM, IA2_RELATION_FLOWS_FROM)
+
+RELATIONTYPE(MEMBER_OF, "member of", ATK_RELATION_MEMBER_OF,
+ NAVRELATION_MEMBER_OF, IA2_RELATION_MEMBER_OF)
+
+RELATIONTYPE(SUBWINDOW_OF, "subwindow of", ATK_RELATION_SUBWINDOW_OF,
+ NAVRELATION_SUBWINDOW_OF, IA2_RELATION_SUBWINDOW_OF)
+
+RELATIONTYPE(EMBEDS, "embeds", ATK_RELATION_EMBEDS, NAVRELATION_EMBEDS,
+ IA2_RELATION_EMBEDS)
+
+RELATIONTYPE(EMBEDDED_BY, "embedded by", ATK_RELATION_EMBEDDED_BY,
+ NAVRELATION_EMBEDDED_BY, IA2_RELATION_EMBEDDED_BY)
+
+RELATIONTYPE(POPUP_FOR, "popup for", ATK_RELATION_POPUP_FOR,
+ NAVRELATION_POPUP_FOR, IA2_RELATION_POPUP_FOR)
+
+RELATIONTYPE(PARENT_WINDOW_OF, "parent window of",
+ ATK_RELATION_PARENT_WINDOW_OF, NAVRELATION_PARENT_WINDOW_OF,
+ IA2_RELATION_PARENT_WINDOW_OF)
+
+RELATIONTYPE(DEFAULT_BUTTON, "default button", ATK_RELATION_NULL,
+ NAVRELATION_DEFAULT_BUTTON, IA2_RELATION_NULL)
+
+RELATIONTYPE(CONTAINING_DOCUMENT, "containing document", ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_DOCUMENT, IA2_RELATION_CONTAINING_DOCUMENT)
+
+RELATIONTYPE(CONTAINING_TAB_PANE, "containing tab pane", ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_TAB_PANE, IA2_RELATION_CONTAINING_TAB_PANE)
+
+RELATIONTYPE(CONTAINING_WINDOW, "containing window", ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_WINDOW, IA2_RELATION_CONTAINING_WINDOW)
+
+RELATIONTYPE(CONTAINING_APPLICATION, "containing application",
+ ATK_RELATION_NULL, NAVRELATION_CONTAINING_APPLICATION,
+ IA2_RELATION_CONTAINING_APPLICATION)
+
+RELATIONTYPE(DETAILS, "details", ATK_RELATION_DETAILS, NAVRELATION_DETAILS,
+ IA2_RELATION_DETAILS)
+
+RELATIONTYPE(DETAILS_FOR, "details for", ATK_RELATION_DETAILS_FOR,
+ NAVRELATION_DETAILS_FOR, IA2_RELATION_DETAILS_FOR)
+
+RELATIONTYPE(ERRORMSG, "error", ATK_RELATION_ERROR_MESSAGE, NAVRELATION_ERROR,
+ IA2_RELATION_ERROR)
+
+RELATIONTYPE(ERRORMSG_FOR, "error for", ATK_RELATION_ERROR_FOR,
+ NAVRELATION_ERROR_FOR, IA2_RELATION_ERROR_FOR)
diff --git a/accessible/base/Role.h b/accessible/base/Role.h
new file mode 100644
index 0000000000..5c0a18cab2
--- /dev/null
+++ b/accessible/base/Role.h
@@ -0,0 +1,1090 @@
+/* -*- 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 _role_h_
+#define _role_h_
+
+/**
+ * @note Make sure to update the localized role names when changing the list.
+ * @note When adding a new role, be sure to also add it to base/RoleMap.h and
+ * update nsIAccessibleRole.
+ */
+
+namespace mozilla {
+namespace a11y {
+namespace roles {
+
+enum Role {
+ /**
+ * Used when accessible hans't strong defined role.
+ */
+ NOTHING = 0,
+
+ /**
+ * Represents a title or caption bar for a window. It is used by MSAA only,
+ * supported automatically by MS Windows.
+ */
+ TITLEBAR = 1,
+
+ /**
+ * Represents the menu bar (positioned beneath the title bar of a window)
+ * from which menus are selected by the user. The role is used by
+ * xul:menubar or role="menubar".
+ */
+ MENUBAR = 2,
+
+ /**
+ * Represents a vertical or horizontal scroll bar, which is part of the client
+ * area or used in a control.
+ */
+ SCROLLBAR = 3,
+
+ /**
+ * Represents a special mouse pointer, which allows a user to manipulate user
+ * interface elements such as windows. For example, a user clicks and drags
+ * a sizing grip in the lower-right corner of a window to resize it.
+ */
+ GRIP = 4,
+
+ /**
+ * Represents a system sound, which is associated with various system events.
+ */
+ SOUND = 5,
+
+ /**
+ * Represents the system mouse pointer.
+ */
+ CURSOR = 6,
+
+ /**
+ * Represents the system caret. The role is supported for caret.
+ */
+ CARET = 7,
+
+ /**
+ * Represents an alert or a condition that a user should be notified about.
+ * Assistive Technologies typically respond to the role by reading the entire
+ * onscreen contents of containers advertising this role. Should be used for
+ * warning dialogs, etc. The role is used by xul:browsermessage,
+ * role="alert".
+ */
+ ALERT = 8,
+
+ /**
+ * Represents the window frame, which contains child objects such as
+ * a title bar, client, and other objects contained in a window. The role
+ * is supported automatically by MS Windows.
+ */
+ WINDOW = 9,
+
+ /**
+ * A sub-document (<frame> or <iframe>)
+ */
+ INTERNAL_FRAME = 10,
+
+ /**
+ * Represents a menu, which presents a list of options from which the user can
+ * make a selection to perform an action. It is used for role="menu".
+ */
+ MENUPOPUP = 11,
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to carry out a command, select an option. It is used for xul:menuitem,
+ * role="menuitem".
+ */
+ MENUITEM = 12,
+
+ /**
+ * Represents a ToolTip that provides helpful hints.
+ */
+ TOOLTIP = 13,
+
+ /**
+ * Represents a main window for an application. It is used for
+ * role="application". Also refer to APP_ROOT
+ */
+ APPLICATION = 14,
+
+ /**
+ * Represents a document window. A document window is always contained within
+ * an application window. For role="document", see NON_NATIVE_DOCUMENT.
+ */
+ DOCUMENT = 15,
+
+ /**
+ * Represents a pane within a frame or document window. Users can navigate
+ * between panes and within the contents of the current pane, but cannot
+ * navigate between items in different panes. Thus, panes represent a level
+ * of grouping lower than frame windows or documents, but above individual
+ * controls. It is used for the first child of a <frame> or <iframe>.
+ */
+ PANE = 16,
+
+ /**
+ * Represents a graphical image used to represent data.
+ */
+ CHART = 17,
+
+ /**
+ * Represents a dialog box or message box. It is used for xul:dialog,
+ * role="dialog".
+ */
+ DIALOG = 18,
+
+ /**
+ * Represents a window border.
+ */
+ BORDER = 19,
+
+ /**
+ * Logically groups other objects. There is not always a parent-child
+ * relationship between the grouping object and the objects it contains. It
+ * is used for html:textfield, xul:groupbox, role="group".
+ */
+ GROUPING = 20,
+
+ /**
+ * Used to visually divide a space into two regions, such as a separator menu
+ * item or a bar that divides split panes within a window. It is used for
+ * xul:separator, html:hr, role="separator".
+ */
+ SEPARATOR = 21,
+
+ /**
+ * Represents a toolbar, which is a grouping of controls (push buttons or
+ * toggle buttons) that provides easy access to frequently used features. It
+ * is used for xul:toolbar, role="toolbar".
+ */
+ TOOLBAR = 22,
+
+ /**
+ * Represents a status bar, which is an area at the bottom of a window that
+ * displays information about the current operation, state of the application,
+ * or selected object. The status bar has multiple fields, which display
+ * different kinds of information. It is used for xul:statusbar.
+ */
+ STATUSBAR = 23,
+
+ /**
+ * Represents a table that contains rows and columns of cells, and optionally,
+ * row headers and column headers. It is used for html:table,
+ * role="grid". Also refer to the following role: COLUMNHEADER,
+ * ROWHEADER, COLUMN, ROW, CELL.
+ */
+ TABLE = 24,
+
+ /**
+ * Represents a column header, providing a visual label for a column in
+ * a table. It is used for XUL tree column headers, html:th,
+ * role="colheader". Also refer to TABLE.
+ */
+ COLUMNHEADER = 25,
+
+ /**
+ * Represents a row header, which provides a visual label for a table row.
+ * It is used for role="rowheader". Also, see TABLE.
+ */
+ ROWHEADER = 26,
+
+ /**
+ * Represents a column of cells within a table. Also, see TABLE.
+ */
+ COLUMN = 27,
+
+ /**
+ * Represents a row of cells within a table. Also, see TABLE.
+ */
+ ROW = 28,
+
+ /**
+ * Represents a cell within a table. It is used for html:td and xul:tree cell.
+ * Also, see TABLE.
+ */
+ CELL = 29,
+
+ /**
+ * Represents a link to something else. This object might look like text or
+ * a graphic, but it acts like a button. It is used for
+ * xul:label@class="text-link", html:a, html:area.
+ */
+ LINK = 30,
+
+ /**
+ * Displays a Help topic in the form of a ToolTip or Help balloon.
+ */
+ HELPBALLOON = 31,
+
+ /**
+ * Represents a cartoon-like graphic object, such as Microsoft Office
+ * Assistant, which is displayed to provide help to users of an application.
+ */
+ CHARACTER = 32,
+
+ /**
+ * Represents a list box, allowing the user to select one or more items. It
+ * is used for xul:listbox, html:select@size, role="list". See also
+ * LIST_ITEM.
+ */
+ LIST = 33,
+
+ /**
+ * Represents an item in a list. See also LIST.
+ */
+ LISTITEM = 34,
+
+ /**
+ * Represents an outline or tree structure, such as a tree view control,
+ * that displays a hierarchical list and allows the user to expand and
+ * collapse branches. Is is used for role="tree".
+ */
+ OUTLINE = 35,
+
+ /**
+ * Represents an item in an outline or tree structure. It is used for
+ * role="treeitem".
+ */
+ OUTLINEITEM = 36,
+
+ /**
+ * Represents a page tab, it is a child of a page tab list. It is used for
+ * xul:tab, role="treeitem". Also refer to PAGETABLIST.
+ */
+ PAGETAB = 37,
+
+ /**
+ * Represents a property sheet. It is used for xul:tabpanel,
+ * role="tabpanel".
+ */
+ PROPERTYPAGE = 38,
+
+ /**
+ * Represents an indicator, such as a pointer graphic, that points to the
+ * current item.
+ */
+ INDICATOR = 39,
+
+ /**
+ * Represents a picture. Is is used for xul:image, html:img.
+ */
+ GRAPHIC = 40,
+
+ /**
+ * Represents read-only text, such as labels for other controls or
+ * instructions in a dialog box. Static text cannot be modified or selected.
+ * Is is used for xul:label, xul:description, html:label, role="label".
+ */
+ STATICTEXT = 41,
+
+ /**
+ * Represents selectable text that allows edits or is designated read-only.
+ */
+ TEXT_LEAF = 42,
+
+ /**
+ * Represents a push button control. It is used for xul:button, html:button,
+ * role="button".
+ */
+ PUSHBUTTON = 43,
+
+ /**
+ * Represents a check box control. It is used for xul:checkbox,
+ * html:input@type="checkbox", role="checkbox".
+ */
+ CHECKBUTTON = 44,
+
+ /**
+ * Represents an option button, also called a radio button. It is one of a
+ * group of mutually exclusive options. All objects sharing a single parent
+ * that have this attribute are assumed to be part of single mutually
+ * exclusive group. It is used for xul:radio, html:input@type="radio",
+ * role="radio".
+ */
+ RADIOBUTTON = 45,
+
+ /**
+ * Represents a combo box; a popup button with an associated list box that
+ * provides a set of predefined choices. It is used for html:select with a
+ * size of 1 and xul:menulist. See also ROLE_EDITCOMBOBOX.
+ */
+ COMBOBOX = 46,
+
+ /**
+ * Represents the calendar control.
+ */
+ DROPLIST = 47,
+
+ /**
+ * Represents a progress bar, dynamically showing the user the percent
+ * complete of an operation in progress. It is used for html:progress,
+ * role="progressbar".
+ */
+ PROGRESSBAR = 48,
+
+ /**
+ * Represents a dial or knob whose purpose is to allow a user to set a value.
+ */
+ DIAL = 49,
+
+ /**
+ * Represents a hot-key field that allows the user to enter a combination or
+ * sequence of keystrokes.
+ */
+ HOTKEYFIELD = 50,
+
+ /**
+ * Represents a slider, which allows the user to adjust a setting in given
+ * increments between minimum and maximum values. It is used by xul:scale,
+ * role="slider".
+ */
+ SLIDER = 51,
+
+ /**
+ * Represents a spin box, which is a control that allows the user to increment
+ * or decrement the value displayed in a separate "buddy" control associated
+ * with the spin box. It is used for input[type=number] spin buttons.
+ */
+ SPINBUTTON = 52,
+
+ /**
+ * Represents a graphical image used to diagram data. It is used for svg:svg.
+ */
+ DIAGRAM = 53,
+
+ /**
+ * Represents an animation control, which contains content that changes over
+ * time, such as a control that displays a series of bitmap frames.
+ */
+ ANIMATION = 54,
+
+ /**
+ * Represents a mathematical equation. It is used by MATHML, where there is a
+ * rich DOM subtree for an equation. Use FLAT_EQUATION for <img role="math"
+ * alt="[TeX]"/>
+ */
+ EQUATION = 55,
+
+ /**
+ * Represents a button that drops down a list of items.
+ */
+ BUTTONDROPDOWN = 56,
+
+ /**
+ * Represents a button that drops down a menu.
+ */
+ BUTTONMENU = 57,
+
+ /**
+ * Represents a button that drops down a grid. It is used for xul:colorpicker.
+ */
+ BUTTONDROPDOWNGRID = 58,
+
+ /**
+ * Represents blank space between other objects.
+ */
+ WHITESPACE = 59,
+
+ /**
+ * Represents a container of page tab controls. Is it used for xul:tabs,
+ * DHTML: role="tabs". Also refer to PAGETAB.
+ */
+ PAGETABLIST = 60,
+
+ /**
+ * Represents a control that displays time.
+ */
+ CLOCK = 61,
+
+ /**
+ * Represents a button on a toolbar that has a drop-down list icon directly
+ * adjacent to the button.
+ */
+ SPLITBUTTON = 62,
+
+ /**
+ * Represents an edit control designed for an Internet Protocol (IP) address.
+ * The edit control is divided into sections for the different parts of the
+ * IP address.
+ */
+ IPADDRESS = 63,
+
+ /**
+ * Represents a label control that has an accelerator.
+ */
+ ACCEL_LABEL = 64,
+
+ /**
+ * Represents an arrow in one of the four cardinal directions.
+ */
+ ARROW = 65,
+
+ /**
+ * Represents a control that can be drawn into and is used to trap events.
+ * It is used for html:canvas.
+ */
+ CANVAS = 66,
+
+ /**
+ * Represents a menu item with a check box.
+ */
+ CHECK_MENU_ITEM = 67,
+
+ /**
+ * Represents a specialized dialog that lets the user choose a color.
+ */
+ COLOR_CHOOSER = 68,
+
+ /**
+ * Represents control whose purpose is to allow a user to edit a date.
+ */
+ DATE_EDITOR = 69,
+
+ /**
+ * An iconified internal frame in an DESKTOP_PANE. Also refer to
+ * INTERNAL_FRAME.
+ */
+ DESKTOP_ICON = 70,
+
+ /**
+ * A desktop pane. A pane that supports internal frames and iconified
+ * versions of those internal frames.
+ */
+ DESKTOP_FRAME = 71,
+
+ /**
+ * A directory pane. A pane that allows the user to navigate through
+ * and select the contents of a directory. May be used by a file chooser.
+ * Also refer to FILE_CHOOSER.
+ */
+ DIRECTORY_PANE = 72,
+
+ /**
+ * A file chooser. A specialized dialog that displays the files in the
+ * directory and lets the user select a file, browse a different directory,
+ * or specify a filename. May use the directory pane to show the contents of
+ * a directory. Also refer to DIRECTORY_PANE.
+ */
+ FILE_CHOOSER = 73,
+
+ /**
+ * A font chooser. A font chooser is a component that lets the user pick
+ * various attributes for fonts.
+ */
+ FONT_CHOOSER = 74,
+
+ /**
+ * Frame role. A top level window with a title bar, border, menu bar, etc.
+ * It is often used as the primary window for an application.
+ */
+ CHROME_WINDOW = 75,
+
+ /**
+ * A glass pane. A pane that is guaranteed to be painted on top of all
+ * panes beneath it. Also refer to ROOT_PANE.
+ */
+ GLASS_PANE = 76,
+
+ /**
+ * A document container for HTML, whose children represent the document
+ * content.
+ */
+ HTML_CONTAINER = 77,
+
+ /**
+ * A small fixed size picture, typically used to decorate components.
+ */
+ ICON = 78,
+
+ /**
+ * Presents an icon or short string in an interface.
+ */
+ LABEL = 79,
+
+ /**
+ * A layered pane. A specialized pane that allows its children to be drawn
+ * in layers, providing a form of stacking order. This is usually the pane
+ * that holds the menu bar as well as the pane that contains most of the
+ * visual components in a window. Also refer to GLASS_PANE and
+ * ROOT_PANE.
+ */
+ LAYERED_PANE = 80,
+
+ /**
+ * A specialized pane whose primary use is inside a dialog.
+ */
+ OPTION_PANE = 81,
+
+ /**
+ * A text object uses for passwords, or other places where the text content
+ * is not shown visibly to the user.
+ */
+ PASSWORD_TEXT = 82,
+
+ /**
+ * A temporary window that is usually used to offer the user a list of
+ * choices, and then hides when the user selects one of those choices.
+ */
+ POPUP_MENU = 83,
+
+ /**
+ * A radio button that is a menu item.
+ */
+ RADIO_MENU_ITEM = 84,
+
+ /**
+ * A root pane. A specialized pane that has a glass pane and a layered pane
+ * as its children. Also refer to GLASS_PANE and LAYERED_PANE.
+ */
+ ROOT_PANE = 85,
+
+ /**
+ * A scroll pane. An object that allows a user to incrementally view a large
+ * amount of information. Its children can include scroll bars and a
+ * viewport. Also refer to VIEW_PORT.
+ */
+ SCROLL_PANE = 86,
+
+ /**
+ * A split pane. A specialized panel that presents two other panels at the
+ * same time. Between the two panels is a divider the user can manipulate to
+ * make one panel larger and the other panel smaller.
+ */
+ SPLIT_PANE = 87,
+
+ /**
+ * The header for a column of a table.
+ * XXX: it looks this role is dupe of COLUMNHEADER.
+ */
+ TABLE_COLUMN_HEADER = 88,
+
+ /**
+ * The header for a row of a table.
+ * XXX: it looks this role is dupe of ROWHEADER
+ */
+ TABLE_ROW_HEADER = 89,
+
+ /**
+ * A menu item used to tear off and reattach its menu.
+ */
+ TEAR_OFF_MENU_ITEM = 90,
+
+ /**
+ * Represents an accessible terminal.
+ */
+ TERMINAL = 91,
+
+ /**
+ * Collection of objects that constitute a logical text entity.
+ */
+ TEXT_CONTAINER = 92,
+
+ /**
+ * A toggle button. A specialized push button that can be checked or
+ * unchecked, but does not provide a separate indicator for the current state.
+ */
+ TOGGLE_BUTTON = 93,
+
+ /**
+ * Represent a control that is capable of expanding and collapsing rows as
+ * well as showing multiple columns of data.
+ */
+ TREE_TABLE = 94,
+
+ /**
+ * A viewport. An object usually used in a scroll pane. It represents the
+ * portion of the entire data that the user can see. As the user manipulates
+ * the scroll bars, the contents of the viewport can change. Also refer to
+ * SCROLL_PANE.
+ */
+ VIEWPORT = 95,
+
+ /**
+ * Header of a document page. Also refer to FOOTER.
+ */
+ HEADER = 96,
+
+ /**
+ * Footer of a document page. Also refer to HEADER.
+ */
+ FOOTER = 97,
+
+ /**
+ * A paragraph of text.
+ */
+ PARAGRAPH = 98,
+
+ /**
+ * A ruler such as those used in word processors.
+ */
+ RULER = 99,
+
+ /**
+ * A text entry having dialog or list containing items for insertion into
+ * an entry widget, for instance a list of words for completion of a
+ * text entry. It is used for xul:textbox@autocomplete
+ */
+ AUTOCOMPLETE = 100,
+
+ /**
+ * An editable text object in a toolbar.
+ */
+ EDITBAR = 101,
+
+ /**
+ * An control whose textual content may be entered or modified by the user.
+ */
+ ENTRY = 102,
+
+ /**
+ * A caption describing another object.
+ */
+ CAPTION = 103,
+
+ /**
+ * An element containing content that assistive technology users may want to
+ * browse in a reading mode, rather than a focus/interactive/application mode.
+ * This role is used for role="document". For the container which holds the
+ * content of a web page, see DOCUMENT.
+ */
+ NON_NATIVE_DOCUMENT = 104,
+
+ /**
+ * Heading.
+ */
+ HEADING = 105,
+
+ /**
+ * An object representing a page of document content. It is used in documents
+ * which are accessed by the user on a page by page basis.
+ */
+ PAGE = 106,
+
+ /**
+ * A container of document content. An example of the use of this role is to
+ * represent an html:div.
+ */
+ SECTION = 107,
+
+ /**
+ * An object which is redundant with another object in the accessible
+ * hierarchy. ATs typically ignore objects with this role.
+ */
+ REDUNDANT_OBJECT = 108,
+
+ /**
+ * A container of form controls. An example of the use of this role is to
+ * represent an html:form.
+ */
+ FORM = 109,
+
+ /**
+ * An object which is used to allow input of characters not found on a
+ * keyboard, such as the input of Chinese characters on a Western keyboard.
+ */
+ IME = 110,
+
+ /**
+ * XXX: document this.
+ */
+ APP_ROOT = 111,
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to display another menu.
+ */
+ PARENT_MENUITEM = 112,
+
+ /**
+ * A calendar that allows the user to select a date.
+ */
+ CALENDAR = 113,
+
+ /**
+ * A list of items that is shown by combobox.
+ */
+ COMBOBOX_LIST = 114,
+
+ /**
+ * A item of list that is shown by combobox.
+ */
+ COMBOBOX_OPTION = 115,
+
+ /**
+ * An image map -- has child links representing the areas
+ */
+ IMAGE_MAP = 116,
+
+ /**
+ * An option in a listbox
+ */
+ OPTION = 117,
+
+ /**
+ * A rich option in a listbox, it can have other widgets as children
+ */
+ RICH_OPTION = 118,
+
+ /**
+ * A list of options
+ */
+ LISTBOX = 119,
+
+ /**
+ * Represents a mathematical equation in the accessible name
+ */
+ FLAT_EQUATION = 120,
+
+ /**
+ * Represents a cell within a grid. It is used for role="gridcell". Unlike
+ * CELL, it allows the calculation of the accessible name from subtree.
+ * Also, see TABLE.
+ */
+ GRID_CELL = 121,
+
+ /**
+ * Represents an embedded object. It is used for html:object or html:embed.
+ */
+ EMBEDDED_OBJECT = 122,
+
+ /**
+ * A note. Originally intended to be hidden until activated, but now also used
+ * for things like html 'aside'.
+ */
+ NOTE = 123,
+
+ /**
+ * A figure. Used for things like HTML5 figure element.
+ */
+ FIGURE = 124,
+
+ /**
+ * Represents a rich item with a check box.
+ */
+ CHECK_RICH_OPTION = 125,
+
+ /**
+ * Represent a definition list (dl in HTML).
+ */
+ DEFINITION_LIST = 126,
+
+ /**
+ * Represent a term in a definition list (dt in HTML).
+ */
+ TERM = 127,
+
+ /**
+ * Represent a definition in a definition list (dd in HTML)
+ */
+ DEFINITION = 128,
+
+ /**
+ * Represent a keyboard or keypad key (ARIA role "key").
+ */
+ KEY = 129,
+
+ /**
+ * Represent a switch control widget (ARIA role "switch").
+ */
+ SWITCH = 130,
+
+ /**
+ * A block of MathML code (math).
+ */
+ MATHML_MATH = 131,
+
+ /**
+ * A MathML identifier (mi in MathML).
+ */
+ MATHML_IDENTIFIER = 132,
+
+ /**
+ * A MathML number (mn in MathML).
+ */
+ MATHML_NUMBER = 133,
+
+ /**
+ * A MathML operator (mo in MathML).
+ */
+ MATHML_OPERATOR = 134,
+
+ /**
+ * A MathML text (mtext in MathML).
+ */
+ MATHML_TEXT = 135,
+
+ /**
+ * A MathML string literal (ms in MathML).
+ */
+ MATHML_STRING_LITERAL = 136,
+
+ /**
+ * A MathML glyph (mglyph in MathML).
+ */
+ MATHML_GLYPH = 137,
+
+ /**
+ * A MathML row (mrow in MathML).
+ */
+ MATHML_ROW = 138,
+
+ /**
+ * A MathML fraction (mfrac in MathML).
+ */
+ MATHML_FRACTION = 139,
+
+ /**
+ * A MathML square root (msqrt in MathML).
+ */
+ MATHML_SQUARE_ROOT = 140,
+
+ /**
+ * A MathML root (mroot in MathML).
+ */
+ MATHML_ROOT = 141,
+
+ /**
+ * A MathML fenced element (mfenced in MathML).
+ */
+ MATHML_FENCED = 142,
+
+ /**
+ * A MathML enclosed element (menclose in MathML).
+ */
+ MATHML_ENCLOSED = 143,
+
+ /**
+ * A MathML styling element (mstyle in MathML).
+ */
+ MATHML_STYLE = 144,
+
+ /**
+ * A MathML subscript (msub in MathML).
+ */
+ MATHML_SUB = 145,
+
+ /**
+ * A MathML superscript (msup in MathML).
+ */
+ MATHML_SUP = 146,
+
+ /**
+ * A MathML subscript and superscript (msubsup in MathML).
+ */
+ MATHML_SUB_SUP = 147,
+
+ /**
+ * A MathML underscript (munder in MathML).
+ */
+ MATHML_UNDER = 148,
+
+ /**
+ * A MathML overscript (mover in MathML).
+ */
+ MATHML_OVER = 149,
+
+ /**
+ * A MathML underscript and overscript (munderover in MathML).
+ */
+ MATHML_UNDER_OVER = 150,
+
+ /**
+ * A MathML multiple subscript and superscript element (mmultiscripts in
+ * MathML).
+ */
+ MATHML_MULTISCRIPTS = 151,
+
+ /**
+ * A MathML table (mtable in MathML).
+ */
+ MATHML_TABLE = 152,
+
+ /**
+ * A MathML labelled table row (mlabeledtr in MathML).
+ */
+ MATHML_LABELED_ROW = 153,
+
+ /**
+ * A MathML table row (mtr in MathML).
+ */
+ MATHML_TABLE_ROW = 154,
+
+ /**
+ * A MathML table entry or cell (mtd in MathML).
+ */
+ MATHML_CELL = 155,
+
+ /**
+ * A MathML interactive element (maction in MathML).
+ */
+ MATHML_ACTION = 156,
+
+ /**
+ * A MathML error message (merror in MathML).
+ */
+ MATHML_ERROR = 157,
+
+ /**
+ * A MathML stacked (rows of numbers) element (mstack in MathML).
+ */
+ MATHML_STACK = 158,
+
+ /**
+ * A MathML long division element (mlongdiv in MathML).
+ */
+ MATHML_LONG_DIVISION = 159,
+
+ /**
+ * A MathML stack group (msgroup in MathML).
+ */
+ MATHML_STACK_GROUP = 160,
+
+ /**
+ * A MathML stack row (msrow in MathML).
+ */
+ MATHML_STACK_ROW = 161,
+
+ /**
+ * MathML carries, borrows, or crossouts for a row (mscarries in MathML).
+ */
+ MATHML_STACK_CARRIES = 162,
+
+ /**
+ * A MathML carry, borrow, or crossout for a column (mscarry in MathML).
+ */
+ MATHML_STACK_CARRY = 163,
+
+ /**
+ * A MathML line in a stack (msline in MathML).
+ */
+ MATHML_STACK_LINE = 164,
+
+ /**
+ * A group containing radio buttons
+ */
+ RADIO_GROUP = 165,
+
+ /**
+ * A text container exposing brief amount of information. See related
+ * TEXT_CONTAINER role.
+ */
+ TEXT = 166,
+
+ /**
+ * The html:details element.
+ */
+ DETAILS = 167,
+
+ /**
+ * The html:summary element.
+ */
+ SUMMARY = 168,
+
+ /**
+ * An ARIA landmark. See related NAVIGATION role.
+ */
+ LANDMARK = 169,
+
+ /**
+ * A specific type of ARIA landmark. The ability to distinguish navigation
+ * landmarks from other types of landmarks is, for example, needed on macOS
+ * where specific AXSubrole and AXRoleDescription for navigation landmarks
+ * are used.
+ */
+ NAVIGATION = 170,
+
+ /**
+ * An object that contains the text of a footnote.
+ */
+ FOOTNOTE = 171,
+
+ /**
+ * A complete or self-contained composition in a document, page, application,
+ * or site and that is, in principle, independently distributable or reusable,
+ * e.g. in syndication.
+ */
+ ARTICLE = 172,
+
+ /**
+ * A perceivable section containing content that is relevant to a specific,
+ * author-specified purpose and sufficiently important that users will likely
+ * want to be able to navigate to the section easily and to have it listed in
+ * a summary of the page.
+ */
+ REGION = 173,
+
+ /**
+ * Represents a control with a text input and a popup with a set of predefined
+ * choices. It is used for ARIA's combobox role. See also COMBOBOX.
+ */
+ EDITCOMBOBOX = 174,
+
+ /**
+ * A section of content that is quoted from another source.
+ */
+ BLOCKQUOTE = 175,
+
+ /**
+ * Content previously deleted or proposed for deletion, e.g. in revision
+ * history or a content view providing suggestions from reviewers.
+ */
+ CONTENT_DELETION = 176,
+
+ /**
+ * Content previously inserted or proposed for insertion, e.g. in revision
+ * history or a content view providing suggestions from reviewers.
+ */
+ CONTENT_INSERTION = 177,
+
+ /**
+ * An html:form element with a label provided by WAI-ARIA.
+ * This may also be used if role="form" with a label should be exposed
+ * differently in the future.
+ */
+ FORM_LANDMARK = 178,
+
+ /**
+ * The html:mark element.
+ * This is also used for the equivalent WAI-ARIA role.
+ */
+ MARK = 179,
+
+ /**
+ * The WAI-ARIA suggestion role.
+ */
+ SUGGESTION = 180,
+
+ /**
+ * The WAI-ARIA comment role.
+ */
+ COMMENT = 181,
+
+ /**
+ * A snippet of program code. ATs might want to treat this differently.
+ */
+ CODE = 182,
+
+ /**
+ * Represents control whose purpose is to allow a user to edit a time.
+ */
+ TIME_EDITOR = 183,
+
+ /**
+ * Represents the marker associated with a list item. In unordered lists,
+ * this is a bullet, while in ordered lists this is a number.
+ */
+ LISTITEM_MARKER = 184,
+
+ LAST_ROLE = LISTITEM_MARKER
+};
+
+} // namespace roles
+
+typedef enum mozilla::a11y::roles::Role role;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/RoleMap.h b/accessible/base/RoleMap.h
new file mode 100644
index 0000000000..8dbba7e8ec
--- /dev/null
+++ b/accessible/base/RoleMap.h
@@ -0,0 +1,1879 @@
+/* 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/. */
+
+// clang-format off
+/**
+ * Usage: declare the macro ROLE()with the following arguments:
+ * ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, ia2Role, nameRule)
+ */
+
+ROLE(NOTHING,
+ "nothing",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TITLEBAR,
+ "titlebar",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Irrelevant on OS X; windows are always native.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TITLEBAR,
+ ROLE_SYSTEM_TITLEBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MENUBAR,
+ "menubar",
+ ATK_ROLE_MENU_BAR,
+ NSAccessibilityMenuBarRole, //Irrelevant on OS X; the menubar will always be native and on the top of the screen.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUBAR,
+ ROLE_SYSTEM_MENUBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SCROLLBAR,
+ "scrollbar",
+ ATK_ROLE_SCROLL_BAR,
+ NSAccessibilityScrollBarRole, //We might need to make this its own mozAccessible, to support the children objects (valueindicator, down/up buttons).
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_SCROLLBAR,
+ ROLE_SYSTEM_SCROLLBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromValueRule)
+
+ROLE(GRIP,
+ "grip",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilitySplitterRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GRIP,
+ ROLE_SYSTEM_GRIP,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SOUND,
+ "sound",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_SOUND,
+ ROLE_SYSTEM_SOUND,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CURSOR,
+ "cursor",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CURSOR,
+ ROLE_SYSTEM_CURSOR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CARET,
+ "caret",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CARET,
+ ROLE_SYSTEM_CARET,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(ALERT,
+ "alert",
+ ATK_ROLE_ALERT,
+ NSAccessibilityGroupRole,
+ @"AXApplicationAlert",
+ ROLE_SYSTEM_ALERT,
+ ROLE_SYSTEM_ALERT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(WINDOW,
+ "window",
+ ATK_ROLE_WINDOW,
+ NSAccessibilityWindowRole, //Irrelevant on OS X; all window a11y is handled by the system.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_WINDOW,
+ ROLE_SYSTEM_WINDOW,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(INTERNAL_FRAME,
+ "internal frame",
+ ATK_ROLE_INTERNAL_FRAME,
+ NSAccessibilityScrollAreaRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INTERNAL_FRAME,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MENUPOPUP,
+ "menupopup",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole, //The parent of menuitems.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MENUITEM,
+ "menuitem",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ java::SessionAccessibility::CLASSNAME_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(TOOLTIP,
+ "tooltip",
+ ATK_ROLE_TOOL_TIP,
+ NSAccessibilityGroupRole,
+ @"AXUserInterfaceTooltip",
+ ROLE_SYSTEM_TOOLTIP,
+ ROLE_SYSTEM_TOOLTIP,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(APPLICATION,
+ "application",
+ ATK_ROLE_EMBEDDED,
+ NSAccessibilityGroupRole, //Unused on OS X. the system will take care of this.
+ @"AXLandmarkApplication",
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(DOCUMENT,
+ "document",
+ ATK_ROLE_DOCUMENT_WEB,
+ @"AXWebArea",
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DOCUMENT,
+ ROLE_SYSTEM_DOCUMENT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+/**
+ * msaa comment:
+ * We used to map to ROLE_SYSTEM_PANE, but JAWS would
+ * not read the accessible name for the contaning pane.
+ * However, JAWS will read the accessible name for a groupbox.
+ * By mapping a PANE to a GROUPING, we get no undesirable effects,
+ * but fortunately JAWS will then read the group's label,
+ * when an inner control gets focused.
+ */
+ROLE(PANE,
+ "pane",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CHART,
+ "chart",
+ ATK_ROLE_CHART,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CHART,
+ ROLE_SYSTEM_CHART,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(DIALOG,
+ "dialog",
+ ATK_ROLE_DIALOG,
+ NSAccessibilityGroupRole, //There's a dialog subrole.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DIALOG,
+ ROLE_SYSTEM_DIALOG,
+ java::SessionAccessibility::CLASSNAME_DIALOG,
+ eNoNameRule)
+
+ROLE(BORDER,
+ "border",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_BORDER,
+ ROLE_SYSTEM_BORDER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(GROUPING,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(SEPARATOR,
+ "separator",
+ ATK_ROLE_SEPARATOR,
+ NSAccessibilitySplitterRole,
+ @"AXContentSeparator",
+ ROLE_SYSTEM_SEPARATOR,
+ ROLE_SYSTEM_SEPARATOR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TOOLBAR,
+ "toolbar",
+ ATK_ROLE_TOOL_BAR,
+ NSAccessibilityToolbarRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TOOLBAR,
+ ROLE_SYSTEM_TOOLBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(STATUSBAR,
+ "statusbar",
+ ATK_ROLE_STATUSBAR,
+ NSAccessibilityGroupRole,
+ @"AXApplicationStatus",
+ ROLE_SYSTEM_STATUSBAR,
+ ROLE_SYSTEM_STATUSBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TABLE,
+ "table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityTableRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TABLE,
+ ROLE_SYSTEM_TABLE,
+ java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(COLUMNHEADER,
+ "columnheader",
+ ATK_ROLE_COLUMN_HEADER,
+ NSAccessibilityCellRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(ROWHEADER,
+ "rowheader",
+ ATK_ROLE_ROW_HEADER,
+ NSAccessibilityCellRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(COLUMN,
+ "column",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityColumnRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COLUMN,
+ ROLE_SYSTEM_COLUMN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(ROW,
+ "row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityRowRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_ROW,
+ ROLE_SYSTEM_ROW,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(CELL,
+ "cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityCellRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LINK,
+ "link",
+ ATK_ROLE_LINK,
+ NSAccessibilityLinkRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LINK,
+ ROLE_SYSTEM_LINK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(HELPBALLOON,
+ "helpballoon",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityHelpTagRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_HELPBALLOON,
+ ROLE_SYSTEM_HELPBALLOON,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(CHARACTER,
+ "character",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CHARACTER,
+ ROLE_SYSTEM_CHARACTER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(LIST,
+ "list",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ NSAccessibilityContentListSubrole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ java::SessionAccessibility::CLASSNAME_LISTVIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LISTITEM,
+ "listitem",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(OUTLINE,
+ "outline",
+ ATK_ROLE_TREE,
+ NSAccessibilityOutlineRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(OUTLINEITEM,
+ "outlineitem",
+ ATK_ROLE_TREE_ITEM,
+ NSAccessibilityRowRole,
+ NSAccessibilityOutlineRowSubrole,
+ ROLE_SYSTEM_OUTLINEITEM,
+ ROLE_SYSTEM_OUTLINEITEM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(PAGETAB,
+ "pagetab",
+ ATK_ROLE_PAGE_TAB,
+ NSAccessibilityRadioButtonRole,
+ @"AXTabButton", // Can be upgraded to NSAccessibilityTabButtonSubrole in 10.13
+ ROLE_SYSTEM_PAGETAB,
+ ROLE_SYSTEM_PAGETAB,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(PROPERTYPAGE,
+ "propertypage",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityGroupRole,
+ @"AXTabPanel",
+ ROLE_SYSTEM_PROPERTYPAGE,
+ ROLE_SYSTEM_PROPERTYPAGE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(INDICATOR,
+ "indicator",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(GRAPHIC,
+ "graphic",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityImageRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ java::SessionAccessibility::CLASSNAME_IMAGE,
+ eNoNameRule)
+
+ROLE(STATICTEXT,
+ "statictext",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TEXT_LEAF,
+ "text leaf",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PUSHBUTTON,
+ "pushbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ java::SessionAccessibility::CLASSNAME_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(CHECKBUTTON,
+ "checkbutton",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ eNameFromSubtreeRule)
+
+ROLE(RADIOBUTTON,
+ "radiobutton",
+ ATK_ROLE_RADIO_BUTTON,
+ NSAccessibilityRadioButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_RADIOBUTTON,
+ ROLE_SYSTEM_RADIOBUTTON,
+ java::SessionAccessibility::CLASSNAME_RADIOBUTTON,
+ eNameFromSubtreeRule)
+
+// Equivalent of HTML select element with size="1". See also EDITCOMBOBOX.
+ROLE(COMBOBOX,
+ "combobox",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ java::SessionAccessibility::CLASSNAME_SPINNER,
+ eNameFromValueRule)
+
+ROLE(DROPLIST,
+ "droplist",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DROPLIST,
+ ROLE_SYSTEM_DROPLIST,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PROGRESSBAR,
+ "progressbar",
+ ATK_ROLE_PROGRESS_BAR,
+ NSAccessibilityProgressIndicatorRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PROGRESSBAR,
+ ROLE_SYSTEM_PROGRESSBAR,
+ java::SessionAccessibility::CLASSNAME_PROGRESSBAR,
+ eNameFromValueRule)
+
+ROLE(DIAL,
+ "dial",
+ ATK_ROLE_DIAL,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DIAL,
+ ROLE_SYSTEM_DIAL,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(HOTKEYFIELD,
+ "hotkeyfield",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SLIDER,
+ "slider",
+ ATK_ROLE_SLIDER,
+ NSAccessibilitySliderRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_SLIDER,
+ ROLE_SYSTEM_SLIDER,
+ java::SessionAccessibility::CLASSNAME_SEEKBAR,
+ eNameFromValueRule)
+
+ROLE(SPINBUTTON,
+ "spinbutton",
+ ATK_ROLE_SPIN_BUTTON,
+ NSAccessibilityIncrementorRole, //Subroles: Increment/Decrement.
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_SPINBUTTON,
+ ROLE_SYSTEM_SPINBUTTON,
+ java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ eNameFromValueRule)
+
+ROLE(DIAGRAM,
+ "diagram",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityImageRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DIAGRAM,
+ ROLE_SYSTEM_DIAGRAM,
+ java::SessionAccessibility::CLASSNAME_IMAGE,
+ eNoNameRule)
+
+ROLE(ANIMATION,
+ "animation",
+ ATK_ROLE_ANIMATION,
+ NSAccessibilityUnknownRole,
+ @"AXApplicationMarquee",
+ ROLE_SYSTEM_ANIMATION,
+ ROLE_SYSTEM_ANIMATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(EQUATION,
+ "equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(BUTTONDROPDOWN,
+ "buttondropdown",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityPopUpButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONMENU,
+ "buttonmenu",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityMenuButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_BUTTONMENU,
+ ROLE_SYSTEM_BUTTONMENU,
+ java::SessionAccessibility::CLASSNAME_SPINNER,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONDROPDOWNGRID,
+ "buttondropdowngrid",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(WHITESPACE,
+ "whitespace",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_WHITESPACE,
+ ROLE_SYSTEM_WHITESPACE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PAGETABLIST,
+ "pagetablist",
+ ATK_ROLE_PAGE_TAB_LIST,
+ NSAccessibilityTabGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PAGETABLIST,
+ ROLE_SYSTEM_PAGETABLIST,
+ java::SessionAccessibility::CLASSNAME_TABWIDGET,
+ eNoNameRule)
+
+ROLE(CLOCK,
+ "clock",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CLOCK,
+ ROLE_SYSTEM_CLOCK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SPLITBUTTON,
+ "splitbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_SPLITBUTTON,
+ ROLE_SYSTEM_SPLITBUTTON,
+ java::SessionAccessibility::CLASSNAME_BUTTON,
+ eNoNameRule)
+
+ROLE(IPADDRESS,
+ "ipaddress",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_IPADDRESS,
+ ROLE_SYSTEM_IPADDRESS,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(ACCEL_LABEL,
+ "accel label",
+ ATK_ROLE_ACCEL_LABEL,
+ NSAccessibilityStaticTextRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(ARROW,
+ "arrow",
+ ATK_ROLE_ARROW,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CANVAS,
+ "canvas",
+ ATK_ROLE_CANVAS,
+ NSAccessibilityImageRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CANVAS,
+ java::SessionAccessibility::CLASSNAME_IMAGE,
+ eNoNameRule)
+
+ROLE(CHECK_MENU_ITEM,
+ "check menu item",
+ ATK_ROLE_CHECK_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_CHECK_MENU_ITEM,
+ java::SessionAccessibility::CLASSNAME_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(COLOR_CHOOSER,
+ "color chooser",
+ ATK_ROLE_COLOR_CHOOSER,
+ NSAccessibilityColorWellRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_DIALOG,
+ IA2_ROLE_COLOR_CHOOSER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(DATE_EDITOR,
+ "date editor",
+ ATK_ROLE_DATE_EDITOR,
+ @"AXDateField",
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DATE_EDITOR,
+ java::SessionAccessibility::CLASSNAME_SPINNER,
+ eNoNameRule)
+
+ROLE(DESKTOP_ICON,
+ "desktop icon",
+ ATK_ROLE_DESKTOP_ICON,
+ NSAccessibilityImageRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_ICON,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(DESKTOP_FRAME,
+ "desktop frame",
+ ATK_ROLE_DESKTOP_FRAME,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(DIRECTORY_PANE,
+ "directory pane",
+ ATK_ROLE_DIRECTORY_PANE,
+ NSAccessibilityBrowserRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DIRECTORY_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FILE_CHOOSER,
+ "file chooser",
+ ATK_ROLE_FILE_CHOOSER,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FILE_CHOOSER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FONT_CHOOSER,
+ "font chooser",
+ ATK_ROLE_FONT_CHOOSER,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FONT_CHOOSER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CHROME_WINDOW,
+ "chrome window",
+ ATK_ROLE_FRAME,
+ NSAccessibilityGroupRole, //Contains the main Firefox UI
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_APPLICATION,
+ IA2_ROLE_FRAME,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(GLASS_PANE,
+ "glass pane",
+ ATK_ROLE_GLASS_PANE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_GLASS_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(HTML_CONTAINER,
+ "html container",
+ ATK_ROLE_HTML_CONTAINER,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(ICON,
+ "icon",
+ ATK_ROLE_ICON,
+ NSAccessibilityImageRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_ICON,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(LABEL,
+ "label",
+ ATK_ROLE_LABEL,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_STATICTEXT,
+ IA2_ROLE_LABEL,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(LAYERED_PANE,
+ "layered pane",
+ ATK_ROLE_LAYERED_PANE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_LAYERED_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(OPTION_PANE,
+ "option pane",
+ ATK_ROLE_OPTION_PANE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_OPTION_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PASSWORD_TEXT,
+ "password text",
+ ATK_ROLE_PASSWORD_TEXT,
+ NSAccessibilityTextFieldRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ eNoNameRule)
+
+ROLE(POPUP_MENU,
+ "popup menu",
+ ATK_ROLE_POPUP_MENU,
+ NSAccessibilityUnknownRole, //Unused
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(RADIO_MENU_ITEM,
+ "radio menu item",
+ ATK_ROLE_RADIO_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_RADIO_MENU_ITEM,
+ java::SessionAccessibility::CLASSNAME_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(ROOT_PANE,
+ "root pane",
+ ATK_ROLE_ROOT_PANE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_ROOT_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SCROLL_PANE,
+ "scroll pane",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityScrollAreaRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SCROLL_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SPLIT_PANE,
+ "split pane",
+ ATK_ROLE_SPLIT_PANE,
+ NSAccessibilitySplitGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SPLIT_PANE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TABLE_COLUMN_HEADER,
+ "table column header",
+ ATK_ROLE_TABLE_COLUMN_HEADER,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(TABLE_ROW_HEADER,
+ "table row header",
+ ATK_ROLE_TABLE_ROW_HEADER,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(TEAR_OFF_MENU_ITEM,
+ "tear off menu item",
+ ATK_ROLE_TEAR_OFF_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_TEAR_OFF_MENU,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(TERMINAL,
+ "terminal",
+ ATK_ROLE_TERMINAL,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TERMINAL,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TEXT_CONTAINER,
+ "text container",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TOGGLE_BUTTON,
+ "toggle button",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityCheckBoxRole,
+ NSAccessibilityToggleSubrole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ java::SessionAccessibility::CLASSNAME_TOGGLEBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(TREE_TABLE,
+ "tree table",
+ ATK_ROLE_TREE_TABLE,
+ NSAccessibilityTableRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ eNoNameRule)
+
+ROLE(VIEWPORT,
+ "viewport",
+ ATK_ROLE_VIEWPORT,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PANE,
+ IA2_ROLE_VIEW_PORT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(HEADER,
+ "header",
+ ATK_ROLE_HEADER,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FOOTER,
+ "footer",
+ ATK_ROLE_FOOTER,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FOOTER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PARAGRAPH,
+ "paragraph",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(RULER,
+ "ruler",
+ ATK_ROLE_RULER,
+ NSAccessibilityRulerRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_RULER,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(AUTOCOMPLETE,
+ "autocomplete",
+ ATK_ROLE_AUTOCOMPLETE,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ eNoNameRule)
+
+ROLE(EDITBAR,
+ "editbar",
+ ATK_ROLE_EDITBAR,
+ NSAccessibilityTextFieldRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TEXT,
+ IA2_ROLE_EDITBAR,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(ENTRY,
+ "entry",
+ ATK_ROLE_ENTRY,
+ NSAccessibilityTextFieldRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ eNameFromValueRule)
+
+ROLE(CAPTION,
+ "caption",
+ ATK_ROLE_CAPTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CAPTION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(NON_NATIVE_DOCUMENT,
+ "non-native document",
+ ATK_ROLE_DOCUMENT_FRAME,
+ NSAccessibilityGroupRole,
+ @"AXDocument",
+ ROLE_SYSTEM_DOCUMENT,
+ ROLE_SYSTEM_DOCUMENT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(HEADING,
+ "heading",
+ ATK_ROLE_HEADING,
+ @"AXHeading",
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(PAGE,
+ "page",
+ ATK_ROLE_PAGE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PAGE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SECTION,
+ "section",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SECTION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(REDUNDANT_OBJECT,
+ "redundant object",
+ ATK_ROLE_REDUNDANT_OBJECT,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_REDUNDANT_OBJECT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FORM,
+ "form",
+ ATK_ROLE_FORM,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FORM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(IME,
+ "ime",
+ ATK_ROLE_INPUT_METHOD_WINDOW,
+ NSAccessibilityUnknownRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INPUT_METHOD_WINDOW,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(APP_ROOT,
+ "app root",
+ ATK_ROLE_APPLICATION,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(PARENT_MENUITEM,
+ "parent menuitem",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ java::SessionAccessibility::CLASSNAME_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(CALENDAR,
+ "calendar",
+ ATK_ROLE_CALENDAR,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CLIENT,
+ ROLE_SYSTEM_CLIENT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(COMBOBOX_LIST,
+ "combobox list",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(COMBOBOX_OPTION,
+ "combobox option",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ java::SessionAccessibility::CLASSNAME_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(IMAGE_MAP,
+ "image map",
+ ATK_ROLE_IMAGE,
+ @"AXImageMap",
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ java::SessionAccessibility::CLASSNAME_IMAGE,
+ eNoNameRule)
+
+ROLE(OPTION,
+ "listbox option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityStaticTextRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(RICH_OPTION,
+ "listbox rich option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityRowRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(LISTBOX,
+ "listbox",
+ ATK_ROLE_LIST_BOX,
+ NSAccessibilityListRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ java::SessionAccessibility::CLASSNAME_LISTVIEW,
+ eNoNameRule)
+
+ROLE(FLAT_EQUATION,
+ "flat equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ @"AXDocumentMath",
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(GRID_CELL,
+ "gridcell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityCellRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(EMBEDDED_OBJECT,
+ "embedded object",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_EMBEDDED_OBJECT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(NOTE,
+ "note",
+ ATK_ROLE_COMMENT,
+ NSAccessibilityGroupRole,
+ @"AXDocumentNote",
+ USE_ROLE_STRING,
+ IA2_ROLE_NOTE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(FIGURE,
+ "figure",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CHECK_RICH_OPTION,
+ "check rich option",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION_LIST,
+ "definitionlist",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ @"AXDescriptionList",
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ java::SessionAccessibility::CLASSNAME_LISTVIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TERM,
+ "term",
+ ATK_ROLE_DESCRIPTION_TERM,
+ NSAccessibilityGroupRole,
+ @"AXTerm",
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION,
+ "definition",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ @"AXDescription",
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(KEY,
+ "key",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ java::SessionAccessibility::CLASSNAME_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(SWITCH,
+ "switch",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityCheckBoxRole,
+ NSAccessibilitySwitchSubrole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ java::SessionAccessibility::CLASSNAME_CHECKBOX,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_MATH,
+ "math",
+ ATK_ROLE_MATH,
+ NSAccessibilityGroupRole,
+ @"AXDocumentMath",
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_IDENTIFIER,
+ "mathml identifier",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ @"AXMathIdentifier",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_NUMBER,
+ "mathml number",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ @"AXMathNumber",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_OPERATOR,
+ "mathml operator",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ @"AXMathOperator",
+ // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and
+ // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and
+ // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
+ // are only available from the MathML layout code. Hence we just fallback
+ // to subrole AXMathOperator for now.
+ // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
+ // which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_TEXT,
+ "mathml text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ @"AXMathRoot",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_STRING_LITERAL,
+ "mathml string literal",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_GLYPH,
+ "mathml glyph",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_IMAGE,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_ROW,
+ "mathml row",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathRow",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_FRACTION,
+ "mathml fraction",
+ ATK_ROLE_MATH_FRACTION,
+ NSAccessibilityGroupRole,
+ @"AXMathFraction",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_SQUARE_ROOT,
+ "mathml square root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ @"AXMathSquareRoot",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_ROOT,
+ "mathml root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ @"AXMathRoot",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_FENCED,
+ "mathml fenced",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathFenced", // XXX bug 1176970 This should be AXMathFence, but doing so without implementing the whole fence interface seems to make VoiceOver crash, so we present it as a row for now.
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_ENCLOSED,
+ "mathml enclosed",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STYLE,
+ "mathml style",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathRow",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_SUB,
+ "mathml sub",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathSubscriptSuperscript",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_SUP,
+ "mathml sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathSubscriptSuperscript",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_SUB_SUP,
+ "mathml sub sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathSubscriptSuperscript",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER,
+ "mathml under",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathUnderOver",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_OVER,
+ "mathml over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathUnderOver",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER_OVER,
+ "mathml under over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathUnderOver",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_MULTISCRIPTS,
+ "mathml multiscripts",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathMultiscript",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE,
+ "mathml table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityGroupRole,
+ @"AXMathTable",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_GRIDVIEW,
+ eNoNameRule)
+
+ROLE(MATHML_LABELED_ROW,
+ "mathml labeled row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE_ROW,
+ "mathml table row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ @"AXMathTableRow",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_CELL,
+ "mathml cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityGroupRole,
+ @"AXMathTableCell",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_ACTION,
+ "mathml action",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_ERROR,
+ "mathml error",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXMathRow",
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK,
+ "mathml stack",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_LONG_DIVISION,
+ "mathml long division",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_GROUP,
+ "mathml stack group",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_ROW,
+ "mathml stack row",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRIES,
+ "mathml stack carries",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRY,
+ "mathml stack carry",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_LINE,
+ "mathml stack line",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(RADIO_GROUP,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityRadioGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TEXT,
+ "text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(DETAILS,
+ "details",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ @"AXDetails",
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(SUMMARY,
+ "summary",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ @"AXSummary",
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ java::SessionAccessibility::CLASSNAME_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(LANDMARK,
+ "landmark",
+ ATK_ROLE_LANDMARK,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_LANDMARK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(NAVIGATION,
+ "navigation",
+ ATK_ROLE_LANDMARK,
+ NSAccessibilityGroupRole,
+ @"AXLandmarkNavigation",
+ USE_ROLE_STRING,
+ IA2_ROLE_LANDMARK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FOOTNOTE,
+ "footnote",
+ ATK_ROLE_FOOTNOTE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FOOTNOTE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(ARTICLE,
+ "article",
+ ATK_ROLE_ARTICLE,
+ NSAccessibilityGroupRole,
+ @"AXDocumentArticle",
+ ROLE_SYSTEM_DOCUMENT,
+ ROLE_SYSTEM_DOCUMENT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(REGION,
+ "region",
+ ATK_ROLE_LANDMARK,
+ NSAccessibilityGroupRole,
+ @"AXLandmarkRegion",
+ USE_ROLE_STRING,
+ IA2_ROLE_LANDMARK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+// A composite widget with a text input and popup. Used for ARIA role combobox.
+// See also COMBOBOX.
+ROLE(EDITCOMBOBOX,
+ "editcombobox",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityComboBoxRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ java::SessionAccessibility::CLASSNAME_EDITTEXT,
+ eNameFromValueRule)
+
+ROLE(BLOCKQUOTE,
+ "blockquote",
+ ATK_ROLE_BLOCK_QUOTE,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ IA2_ROLE_BLOCK_QUOTE,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CONTENT_DELETION,
+ "content deletion",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXDeleteStyleGroup",
+ USE_ROLE_STRING,
+ IA2_ROLE_CONTENT_DELETION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CONTENT_INSERTION,
+ "content insertion",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ @"AXInsertStyleGroup",
+ USE_ROLE_STRING,
+ IA2_ROLE_CONTENT_INSERTION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(FORM_LANDMARK,
+ "form",
+ ATK_ROLE_LANDMARK,
+ NSAccessibilityGroupRole,
+ @"AXLandmarkForm",
+ USE_ROLE_STRING,
+ IA2_ROLE_FORM,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(MARK,
+ "mark",
+ ATK_ROLE_MARK,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_MARK,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(SUGGESTION,
+ "suggestion",
+ ATK_ROLE_SUGGESTION,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SUGGESTION,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(COMMENT,
+ "comment",
+ ATK_ROLE_COMMENT,
+ NSAccessibilityGroupRole,
+ NSAccessibilityUnknownSubrole,
+ USE_ROLE_STRING,
+ IA2_ROLE_COMMENT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(CODE,
+ "code",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ @"AXCodeStyleGroup",
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+
+ROLE(TIME_EDITOR,
+ "time editor",
+ ATK_ROLE_PANEL,
+ @"AXTimeField",
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LISTITEM_MARKER,
+ "list item marker",
+ ATK_ROLE_UNKNOWN,
+ @"AXListMarker",
+ NSAccessibilityUnknownSubrole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ java::SessionAccessibility::CLASSNAME_VIEW,
+ eNoNameRule)
+// clang-format on
diff --git a/accessible/base/SelectionManager.cpp b/accessible/base/SelectionManager.cpp
new file mode 100644
index 0000000000..0c95f6a206
--- /dev/null
+++ b/accessible/base/SelectionManager.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/a11y/SelectionManager.h"
+
+#include "DocAccessible-inl.h"
+#include "HyperTextAccessible.h"
+#include "HyperTextAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "nsFrameSelection.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using mozilla::dom::Selection;
+
+struct mozilla::a11y::SelData final {
+ SelData(Selection* aSel, int32_t aReason) : mSel(aSel), mReason(aReason) {}
+
+ RefPtr<Selection> mSel;
+ int16_t mReason;
+
+ NS_INLINE_DECL_REFCOUNTING(SelData)
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~SelData() {}
+};
+
+SelectionManager::SelectionManager()
+ : mCaretOffset(-1), mAccWithCaret(nullptr) {}
+
+void SelectionManager::ClearControlSelectionListener() {
+ // Remove 'this' registered as selection listener for the normal selection.
+ if (mCurrCtrlNormalSel) {
+ mCurrCtrlNormalSel->RemoveSelectionListener(this);
+ mCurrCtrlNormalSel = nullptr;
+ }
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ if (mCurrCtrlSpellSel) {
+ mCurrCtrlSpellSel->RemoveSelectionListener(this);
+ mCurrCtrlSpellSel = nullptr;
+ }
+}
+
+void SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm) {
+ // When focus moves such that the caret is part of a new frame selection
+ // this removes the old selection listener and attaches a new one for
+ // the current focus.
+ ClearControlSelectionListener();
+
+ nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame();
+ if (!controlFrame) return;
+
+ const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection();
+ NS_ASSERTION(frameSel, "No frame selection for focused element!");
+ if (!frameSel) return;
+
+ // Register 'this' as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AddSelectionListener(this);
+ mCurrCtrlNormalSel = normalSel;
+
+ // Register 'this' as selection listener for the spell check selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AddSelectionListener(this);
+ mCurrCtrlSpellSel = spellSel;
+}
+
+void SelectionManager::AddDocSelectionListener(PresShell* aPresShell) {
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Register 'this' as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AddSelectionListener(this);
+
+ // Register 'this' as selection listener for the spell check selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AddSelectionListener(this);
+}
+
+void SelectionManager::RemoveDocSelectionListener(PresShell* aPresShell) {
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Remove 'this' registered as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->RemoveSelectionListener(this);
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->RemoveSelectionListener(this);
+}
+
+void SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent) {
+ // Fire selection change event if it's not pure caret-move selection change,
+ // i.e. the accessible has or had not collapsed selection.
+ AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
+ if (!event->IsCaretMoveOnly()) nsEventShell::FireEvent(aEvent);
+
+ // Fire caret move event if there's a caret in the selection.
+ nsINode* caretCntrNode = nsCoreUtils::GetDOMNodeFromDOMPoint(
+ event->mSel->GetFocusNode(), event->mSel->FocusOffset());
+ if (!caretCntrNode) return;
+
+ HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode);
+ NS_ASSERTION(
+ caretCntr,
+ "No text container for focus while there's one for common ancestor?!");
+ if (!caretCntr) return;
+
+ Selection* selection = caretCntr->DOMSelection();
+
+ // XXX Sometimes we can't get a selection for caretCntr, in that case assume
+ // event->mSel is correct.
+ if (!selection) selection = event->mSel;
+
+ mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(),
+ selection->FocusOffset());
+ mAccWithCaret = caretCntr;
+ if (mCaretOffset != -1) {
+ RefPtr<AccCaretMoveEvent> caretMoveEvent =
+ new AccCaretMoveEvent(caretCntr, mCaretOffset, selection->IsCollapsed(),
+ aEvent->FromUserInput());
+ nsEventShell::FireEvent(caretMoveEvent);
+ }
+}
+
+NS_IMETHODIMP
+SelectionManager::NotifySelectionChanged(dom::Document* aDocument,
+ Selection* aSelection,
+ int16_t aReason) {
+ if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DocAccessible* document = GetAccService()->GetDocAccessible(aDocument);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eSelection))
+ logging::SelChange(aSelection, document, aReason);
+#endif
+
+ if (document) {
+ // Selection manager has longer lifetime than any document accessible,
+ // so that we are guaranteed that the notification is processed before
+ // the selection manager is destroyed.
+ RefPtr<SelData> selData = new SelData(aSelection, aReason);
+ document->HandleNotification<SelectionManager, SelData>(
+ this, &SelectionManager::ProcessSelectionChanged, selData);
+ }
+
+ return NS_OK;
+}
+
+void SelectionManager::ProcessSelectionChanged(SelData* aSelData) {
+ Selection* selection = aSelData->mSel;
+ if (!selection->GetPresShell()) return;
+
+ const nsRange* range = selection->GetAnchorFocusRange();
+ nsINode* cntrNode = nullptr;
+ if (range) {
+ cntrNode = range->GetClosestCommonInclusiveAncestor();
+ }
+
+ if (!cntrNode) {
+ cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
+ if (!cntrNode) {
+ cntrNode = selection->GetPresShell()->GetDocument();
+ NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() ==
+ selection->GetFrameSelection(),
+ "Wrong selection container was used!");
+ }
+ }
+
+ HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode);
+ if (!text) {
+ // FIXME bug 1126649
+ NS_ERROR("We must reach document accessible implementing text interface!");
+ return;
+ }
+
+ if (selection->GetType() == SelectionType::eNormal) {
+ RefPtr<AccEvent> event =
+ new AccTextSelChangeEvent(text, selection, aSelData->mReason);
+ text->Document()->FireDelayedEvent(event);
+
+ } else if (selection->GetType() == SelectionType::eSpellCheck) {
+ // XXX: fire an event for container accessible of the focus/anchor range
+ // of the spelcheck selection.
+ text->Document()->FireDelayedEvent(
+ nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, text);
+ }
+}
+
+SelectionManager::~SelectionManager() = default;
diff --git a/accessible/base/SelectionManager.h b/accessible/base/SelectionManager.h
new file mode 100644
index 0000000000..34a841d525
--- /dev/null
+++ b/accessible/base/SelectionManager.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 4; 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 mozilla_a11y_SelectionManager_h__
+#define mozilla_a11y_SelectionManager_h__
+
+#include "nsISelectionListener.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace dom {
+class Element;
+class Selection;
+} // namespace dom
+
+namespace a11y {
+
+class AccEvent;
+class HyperTextAccessible;
+
+/**
+ * This special accessibility class is for the caret and selection management.
+ * There is only 1 visible caret per top level window. However, there may be
+ * several visible selections.
+ *
+ * The important selections are the one owned by each document, and the one in
+ * the currently focused control.
+ *
+ * On Windows this class is used to move an invisible system caret that
+ * shadows the Mozilla caret. Windows will also automatically map this to
+ * the MSAA caret accessible object (via OBJID_CARET) (as opposed to the root
+ * accessible tree for a window which is retrieved with OBJID_CLIENT).
+ *
+ * For ATK and IAccessible2, this class is used to fire caret move and
+ * selection change events.
+ */
+
+struct SelData;
+
+class SelectionManager : public nsISelectionListener {
+ public:
+ // nsISupports
+ // implemented by derived nsAccessibilityService
+
+ // nsISelectionListener
+ NS_DECL_NSISELECTIONLISTENER
+
+ // SelectionManager
+ void Shutdown() { ClearControlSelectionListener(); }
+
+ /**
+ * Listen to selection events on the focused control.
+ *
+ * Note: only one control's selection events are listened to at a time. This
+ * will remove the previous control's selection listener.
+ */
+ void SetControlSelectionListener(dom::Element* aFocusedElm);
+
+ /**
+ * Stop listening to selection events on the control.
+ */
+ void ClearControlSelectionListener();
+
+ /**
+ * Listen to selection events on the document.
+ */
+ void AddDocSelectionListener(PresShell* aPresShell);
+
+ /**
+ * Stop listening to selection events for a given document
+ */
+ void RemoveDocSelectionListener(PresShell* aPresShell);
+
+ /**
+ * Process delayed event, results in caret move and text selection change
+ * events.
+ */
+ void ProcessTextSelChangeEvent(AccEvent* aEvent);
+
+ /**
+ * Gets the current caret offset/hypertext accessible pair. If there is no
+ * current pair, then returns -1 for the offset and a nullptr for the
+ * accessible.
+ */
+ inline HyperTextAccessible* AccessibleWithCaret(int32_t* aCaret) {
+ if (aCaret) *aCaret = mCaretOffset;
+
+ return mAccWithCaret;
+ }
+
+ /**
+ * Update caret offset when it doesn't go through a caret move event.
+ */
+ inline void UpdateCaretOffset(HyperTextAccessible* aItem, int32_t aOffset) {
+ mAccWithCaret = aItem;
+ mCaretOffset = aOffset;
+ }
+
+ inline void ResetCaretOffset() {
+ mCaretOffset = -1;
+ mAccWithCaret = nullptr;
+ }
+
+ ~SelectionManager();
+
+ protected:
+ SelectionManager();
+
+ /**
+ * Process DOM selection change. Fire selection and caret move events.
+ */
+ void ProcessSelectionChanged(SelData* aSelData);
+
+ private:
+ // Currently focused control.
+ int32_t mCaretOffset;
+ HyperTextAccessible* mAccWithCaret;
+ WeakPtr<dom::Selection> mCurrCtrlNormalSel;
+ WeakPtr<dom::Selection> mCurrCtrlSpellSel;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/States.h b/accessible/base/States.h
new file mode 100644
index 0000000000..5aa7db28cf
--- /dev/null
+++ b/accessible/base/States.h
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set 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 _states_h_
+#define _states_h_
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+namespace states {
+
+/**
+ * The object is disabled, opposite to enabled and sensitive.
+ */
+const uint64_t UNAVAILABLE = ((uint64_t)0x1) << 0;
+
+/**
+ * The object is selected.
+ */
+const uint64_t SELECTED = ((uint64_t)0x1) << 1;
+
+/**
+ * The object has the keyboard focus.
+ */
+const uint64_t FOCUSED = ((uint64_t)0x1) << 2;
+
+/**
+ * The object is pressed.
+ */
+const uint64_t PRESSED = ((uint64_t)0x1) << 3;
+
+/**
+ * The checkable object is checked, applied to check box controls,
+ * @see CHECKABLE and MIXED states.
+ */
+const uint64_t CHECKED = ((uint64_t)0x1) << 4;
+
+/**
+ * Indicates that the state of a three-state check box or tool bar button is
+ * undetermined. The check box is neither checked or unchecked, and is
+ * in the third or mixed state.
+ */
+const uint64_t MIXED = ((uint64_t)0x1) << 5;
+
+/**
+ * The object is designated read-only, so it can't be edited.
+ */
+const uint64_t READONLY = ((uint64_t)0x1) << 6;
+
+/**
+ * The object is hot-tracked by the mouse, which means that its appearance
+ * has changed to indicate that the mouse pointer is located over it.
+ *
+ * This is currently unused.
+ */
+const uint64_t HOTTRACKED = ((uint64_t)0x1) << 7;
+
+/**
+ * This object is the default button in a window.
+ */
+const uint64_t DEFAULT = ((uint64_t)0x1) << 8;
+
+/**
+ * The expandable object's children are displayed, the opposite of collapsed,
+ * applied to trees, list and other controls.
+ * @see COLLAPSED state
+ */
+const uint64_t EXPANDED = ((uint64_t)0x1) << 9;
+
+/**
+ * The expandable object's children are not displayed, the opposite of
+ * expanded, applied to tree lists and other controls,
+ * @see EXPANDED state.
+ */
+const uint64_t COLLAPSED = ((uint64_t)0x1) << 10;
+
+/**
+ * The control or document can not accept input at this time.
+ */
+const uint64_t BUSY = ((uint64_t)0x1) << 11;
+
+/**
+ * The object is out of normal flow, may be outside of boundaries of its
+ * parent.
+ */
+const uint64_t FLOATING = ((uint64_t)0x1) << 12;
+
+/**
+ * The object can be checked.
+ */
+const uint64_t CHECKABLE = ((uint64_t)0x1) << 13;
+
+/**
+ * This object is a graphic which is rapidly changing appearance.
+ */
+const uint64_t ANIMATED = ((uint64_t)0x1) << 14;
+
+/**
+ * The object is programmatically hidden.
+ * So user action like scrolling or switching tabs won't make this visible.
+ */
+const uint64_t INVISIBLE = ((uint64_t)0x1) << 15;
+
+/**
+ * The object is scrolled off screen.
+ * User action such as scrolling or changing tab may make the object
+ * visible.
+ */
+const uint64_t OFFSCREEN = ((uint64_t)0x1) << 16;
+
+/**
+ * The object can be resized.
+ */
+const uint64_t SIZEABLE = ((uint64_t)0x1) << 17;
+
+/**
+ * The object can be moved to a different position.
+ */
+const uint64_t MOVEABLE = ((uint64_t)0x1) << 18;
+
+/**
+ * The object describes itself with speech.
+ * Other speech related assistive technology may want to avoid speaking
+ * information about this object, because the object is already doing this.
+ */
+const uint64_t SELFVOICING = ((uint64_t)0x1) << 19;
+
+/**
+ * The object can have the focus and become focused.
+ */
+const uint64_t FOCUSABLE = ((uint64_t)0x1) << 20;
+
+/**
+ * The object can be selected.
+ */
+const uint64_t SELECTABLE = ((uint64_t)0x1) << 21;
+
+/**
+ * This object is a link.
+ */
+const uint64_t LINKED = ((uint64_t)0x1) << 22;
+
+/**
+ * This is used for links that have been traversed
+ * i.e. the linked page has been visited.
+ */
+const uint64_t TRAVERSED = ((uint64_t)0x1) << 23;
+
+/**
+ * Supports multiple selection.
+ */
+const uint64_t MULTISELECTABLE = ((uint64_t)0x1) << 24;
+
+/**
+ * Supports extended selection.
+ * All objects supporting this are also multipselectable.
+ * This only makes sense for msaa see bug 635690.
+ */
+const uint64_t EXTSELECTABLE = ((uint64_t)0x1) << 25;
+
+/**
+ * The user is required to interact with this object.
+ */
+const uint64_t REQUIRED = ((uint64_t)0x1) << 26;
+
+/**
+ * The object is an alert, notifying the user of something important.
+ */
+const uint64_t ALERT = ((uint64_t)0x1) << 27;
+
+/**
+ * Used for text fields containing invalid values.
+ */
+const uint64_t INVALID = ((uint64_t)0x1) << 28;
+
+/**
+ * The controls value can not be obtained, and is returned as a set of "*"s.
+ */
+const uint64_t PROTECTED = ((uint64_t)0x1) << 29;
+
+/**
+ * The object can be invoked to show a pop up menu or window.
+ */
+const uint64_t HASPOPUP = ((uint64_t)0x1) << 30;
+
+/**
+ * The editable area has some kind of autocompletion.
+ */
+const uint64_t SUPPORTS_AUTOCOMPLETION = ((uint64_t)0x1) << 31;
+
+/**
+ * The object is no longer available to be queried.
+ */
+const uint64_t DEFUNCT = ((uint64_t)0x1) << 32;
+
+/**
+ * The text is selectable, the object must implement the text interface.
+ */
+const uint64_t SELECTABLE_TEXT = ((uint64_t)0x1) << 33;
+
+/**
+ * The text in this object can be edited.
+ */
+const uint64_t EDITABLE = ((uint64_t)0x1) << 34;
+
+/**
+ * This window is currently the active window.
+ */
+const uint64_t ACTIVE = ((uint64_t)0x1) << 35;
+
+/**
+ * Indicates that the object is modal. Modal objects have the behavior
+ * that something must be done with the object before the user can
+ * interact with an object in a different window.
+ */
+const uint64_t MODAL = ((uint64_t)0x1) << 36;
+
+/**
+ * Edit control that can take multiple lines.
+ */
+const uint64_t MULTI_LINE = ((uint64_t)0x1) << 37;
+
+/**
+ * Uses horizontal layout.
+ */
+const uint64_t HORIZONTAL = ((uint64_t)0x1) << 38;
+
+/**
+ * Indicates this object paints every pixel within its rectangular region.
+ */
+const uint64_t OPAQUE1 = ((uint64_t)0x1) << 39;
+
+/**
+ * This text object can only contain 1 line of text.
+ */
+const uint64_t SINGLE_LINE = ((uint64_t)0x1) << 40;
+
+/**
+ * The parent object manages descendants, and this object may only exist
+ * while it is visible or has focus.
+ * For example the focused cell of a table or the current element of a list box
+ * may have this state.
+ */
+const uint64_t TRANSIENT = ((uint64_t)0x1) << 41;
+
+/**
+ * Uses vertical layout.
+ * Especially used for sliders and scrollbars.
+ */
+const uint64_t VERTICAL = ((uint64_t)0x1) << 42;
+
+/**
+ * Object not dead, but not up-to-date either.
+ */
+const uint64_t STALE = ((uint64_t)0x1) << 43;
+
+/**
+ * A widget that is not unavailable.
+ */
+const uint64_t ENABLED = ((uint64_t)0x1) << 44;
+
+/**
+ * Same as ENABLED state for now see bug 636158
+ */
+const uint64_t SENSITIVE = ((uint64_t)0x1) << 45;
+
+/**
+ * The object is expandable, provides a UI to expand/collapse its children
+ * @see EXPANDED and COLLAPSED states.
+ */
+const uint64_t EXPANDABLE = ((uint64_t)0x1) << 46;
+
+/**
+ * The object is pinned, usually indicating it is fixed in place and has
+ * permanence.
+ */
+const uint64_t PINNED = ((uint64_t)0x1) << 47;
+
+/**
+ * The object is the current item within a container or set of related elements.
+ */
+const uint64_t CURRENT = ((uint64_t)0x1) << 48;
+
+/**
+ * Not a real state, used for static assertions.
+ */
+const uint64_t LAST_ENTRY = CURRENT;
+
+} // namespace states
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/Statistics.h b/accessible/base/Statistics.h
new file mode 100644
index 0000000000..19f7166317
--- /dev/null
+++ b/accessible/base/Statistics.h
@@ -0,0 +1,42 @@
+/* -*- 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 A11Y_STATISTICS_H_
+#define A11Y_STATISTICS_H_
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace a11y {
+namespace statistics {
+
+inline void A11yInitialized() {
+ Telemetry::Accumulate(Telemetry::A11Y_INSTANTIATED_FLAG, true);
+}
+
+inline void A11yConsumers(uint32_t aConsumer) {
+ Telemetry::Accumulate(Telemetry::A11Y_CONSUMERS, aConsumer);
+}
+
+/**
+ * Report that ISimpleDOM* has been used.
+ */
+inline void ISimpleDOMUsed() {
+ Telemetry::Accumulate(Telemetry::A11Y_ISIMPLEDOM_USAGE_FLAG, true);
+}
+
+/**
+ * Report that IAccessibleTable has been used.
+ */
+inline void IAccessibleTableUsed() {
+ Telemetry::Accumulate(Telemetry::A11Y_IATABLE_USAGE_FLAG, true);
+}
+
+} // namespace statistics
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/StyleInfo.cpp b/accessible/base/StyleInfo.cpp
new file mode 100644
index 0000000000..294ed50562
--- /dev/null
+++ b/accessible/base/StyleInfo.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set 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 "StyleInfo.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCSSProps.h"
+#include "nsIFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+StyleInfo::StyleInfo(dom::Element* aElement) : mElement(aElement) {
+ mComputedStyle =
+ nsComputedDOMStyle::GetComputedStyleNoFlush(aElement, nullptr);
+}
+
+void StyleInfo::Display(nsAString& aValue) {
+ aValue.Truncate();
+ nsAutoCString value;
+ mComputedStyle->GetComputedPropertyValue(eCSSProperty_display, value);
+ CopyUTF8toUTF16(value, aValue);
+}
+
+void StyleInfo::TextAlign(nsAString& aValue) {
+ aValue.Truncate();
+ nsAutoCString value;
+ mComputedStyle->GetComputedPropertyValue(eCSSProperty_text_align, value);
+ CopyUTF8toUTF16(value, aValue);
+}
+
+void StyleInfo::TextIndent(nsAString& aValue) {
+ aValue.Truncate();
+
+ const auto& textIndent = mComputedStyle->StyleText()->mTextIndent;
+ if (textIndent.ConvertsToLength()) {
+ aValue.AppendFloat(textIndent.ToLengthInCSSPixels());
+ aValue.AppendLiteral("px");
+ return;
+ }
+ if (textIndent.ConvertsToPercentage()) {
+ aValue.AppendFloat(textIndent.ToPercentage() * 100);
+ aValue.AppendLiteral("%");
+ return;
+ }
+ // FIXME: This doesn't handle calc in any meaningful way? Probably should just
+ // use the Servo serialization code...
+ aValue.AppendLiteral("0px");
+}
+
+void StyleInfo::Margin(Side aSide, nsAString& aValue) {
+ MOZ_ASSERT(mElement->GetPrimaryFrame(),
+ " mElement->GetPrimaryFrame() needs to be valid pointer");
+ aValue.Truncate();
+
+ nsIFrame* frame = mElement->GetPrimaryFrame();
+
+ // This is here only to guarantee that we do the same as getComputedStyle
+ // does, so that we don't hit precision errors in tests.
+ auto& margin = frame->StyleMargin()->mMargin.Get(aSide);
+ if (margin.ConvertsToLength()) {
+ aValue.AppendFloat(margin.AsLengthPercentage().ToLengthInCSSPixels());
+ } else {
+ nscoord coordVal = frame->GetUsedMargin().Side(aSide);
+ aValue.AppendFloat(CSSPixel::FromAppUnits(coordVal));
+ }
+
+ aValue.AppendLiteral("px");
+}
+
+void StyleInfo::FormatColor(const nscolor& aValue, nsString& aFormattedValue) {
+ // Combine the string like rgb(R, G, B) from nscolor.
+ aFormattedValue.AppendLiteral("rgb(");
+ aFormattedValue.AppendInt(NS_GET_R(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_G(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_B(aValue));
+ aFormattedValue.Append(')');
+}
+
+void StyleInfo::FormatTextDecorationStyle(uint8_t aValue,
+ nsAString& aFormattedValue) {
+ // TODO: When these are enum classes that rust also understands we should just
+ // make an FFI call here.
+ switch (aValue) {
+ case NS_STYLE_TEXT_DECORATION_STYLE_NONE:
+ return aFormattedValue.AssignASCII("-moz-none");
+ case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+ return aFormattedValue.AssignASCII("solid");
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
+ return aFormattedValue.AssignASCII("double");
+ case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+ return aFormattedValue.AssignASCII("dotted");
+ case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
+ return aFormattedValue.AssignASCII("dashed");
+ case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
+ return aFormattedValue.AssignASCII("wavy");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown decoration style");
+ break;
+ }
+}
diff --git a/accessible/base/StyleInfo.h b/accessible/base/StyleInfo.h
new file mode 100644
index 0000000000..3423f545f3
--- /dev/null
+++ b/accessible/base/StyleInfo.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set 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_style_h_
+#define _mozilla_a11y_style_h_
+
+#include "mozilla/gfx/Types.h"
+#include "mozilla/ComputedStyle.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+namespace a11y {
+
+class StyleInfo {
+ public:
+ explicit StyleInfo(dom::Element* aElement);
+ ~StyleInfo() {}
+
+ void Display(nsAString& aValue);
+ void TextAlign(nsAString& aValue);
+ void TextIndent(nsAString& aValue);
+ void MarginLeft(nsAString& aValue) { Margin(eSideLeft, aValue); }
+ void MarginRight(nsAString& aValue) { Margin(eSideRight, aValue); }
+ void MarginTop(nsAString& aValue) { Margin(eSideTop, aValue); }
+ void MarginBottom(nsAString& aValue) { Margin(eSideBottom, aValue); }
+
+ static void FormatColor(const nscolor& aValue, nsString& aFormattedValue);
+ static void FormatTextDecorationStyle(uint8_t aValue,
+ nsAString& aFormattedValue);
+
+ private:
+ StyleInfo() = delete;
+ StyleInfo(const StyleInfo&) = delete;
+ StyleInfo& operator=(const StyleInfo&) = delete;
+
+ void Margin(Side aSide, nsAString& aValue);
+
+ dom::Element* mElement;
+ RefPtr<ComputedStyle> mComputedStyle;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextAttrs.cpp b/accessible/base/TextAttrs.cpp
new file mode 100644
index 0000000000..869a536c9d
--- /dev/null
+++ b/accessible/base/TextAttrs.cpp
@@ -0,0 +1,750 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TextAttrs.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "StyleInfo.h"
+
+#include "gfxTextRun.h"
+#include "nsFontMetrics.h"
+#include "nsLayoutUtils.h"
+#include "nsContainerFrame.h"
+#include "nsStyleUtil.h"
+#include "HyperTextAccessible.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TextAttrsMgr
+////////////////////////////////////////////////////////////////////////////////
+
+void TextAttrsMgr::GetAttributes(nsIPersistentProperties* aAttributes,
+ uint32_t* aStartOffset, uint32_t* aEndOffset) {
+ // 1. Hyper text accessible must be specified always.
+ // 2. Offset accessible and result hyper text offsets must be specified in
+ // the case of text attributes.
+ // 3. Offset accessible and result hyper text offsets must not be specified
+ // but include default text attributes flag and attributes list must be
+ // specified in the case of default text attributes.
+ MOZ_ASSERT(
+ mHyperTextAcc &&
+ ((mOffsetAcc && mOffsetAccIdx != -1 && aStartOffset && aEndOffset) ||
+ (!mOffsetAcc && mOffsetAccIdx == -1 && !aStartOffset &&
+ !aEndOffset && mIncludeDefAttrs && aAttributes)),
+ "Wrong usage of TextAttrsMgr!");
+
+ // Embedded objects are combined into own range with empty attributes set.
+ if (mOffsetAcc && !mOffsetAcc->IsText()) {
+ for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (currAcc->IsText()) break;
+
+ (*aStartOffset)--;
+ }
+
+ uint32_t childCount = mHyperTextAcc->ChildCount();
+ for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childCount;
+ childIdx++) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (currAcc->IsText()) break;
+
+ (*aEndOffset)++;
+ }
+
+ return;
+ }
+
+ // Get the content and frame of the accessible. In the case of document
+ // accessible it's role content and root frame.
+ nsIContent* hyperTextElm = mHyperTextAcc->GetContent();
+ if (!hyperTextElm)
+ return; // XXX: we don't support text attrs on document with no body
+
+ nsIFrame* rootFrame = mHyperTextAcc->GetFrame();
+ MOZ_ASSERT(rootFrame, "No frame for accessible!");
+ if (!rootFrame) return;
+
+ nsIContent *offsetNode = nullptr, *offsetElm = nullptr;
+ nsIFrame* frame = nullptr;
+ if (mOffsetAcc) {
+ offsetNode = mOffsetAcc->GetContent();
+ offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
+ MOZ_ASSERT(offsetElm, "No element for offset accessible!");
+ if (!offsetElm) return;
+
+ frame = offsetElm->GetPrimaryFrame();
+ }
+
+ // "language" text attribute
+ LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
+
+ // "aria-invalid" text attribute
+ InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode);
+
+ // "background-color" text attribute
+ BGColorTextAttr bgColorTextAttr(rootFrame, frame);
+
+ // "color" text attribute
+ ColorTextAttr colorTextAttr(rootFrame, frame);
+
+ // "font-family" text attribute
+ FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame);
+
+ // "font-size" text attribute
+ FontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
+
+ // "font-style" text attribute
+ FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
+
+ // "font-weight" text attribute
+ FontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
+
+ // "auto-generated" text attribute
+ AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc);
+
+ // "text-underline(line-through)-style(color)" text attributes
+ TextDecorTextAttr textDecorTextAttr(rootFrame, frame);
+
+ // "text-position" text attribute
+ TextPosTextAttr textPosTextAttr(rootFrame, frame);
+
+ TextAttr* attrArray[] = {
+ &langTextAttr, &invalidTextAttr, &bgColorTextAttr,
+ &colorTextAttr, &fontFamilyTextAttr, &fontSizeTextAttr,
+ &fontStyleTextAttr, &fontWeightTextAttr, &autoGenTextAttr,
+ &textDecorTextAttr, &textPosTextAttr};
+
+ // Expose text attributes if applicable.
+ if (aAttributes) {
+ for (uint32_t idx = 0; idx < ArrayLength(attrArray); idx++)
+ attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs);
+ }
+
+ // Expose text attributes range where they are applied if applicable.
+ if (mOffsetAcc)
+ GetRange(attrArray, ArrayLength(attrArray), aStartOffset, aEndOffset);
+}
+
+void TextAttrsMgr::GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
+ uint32_t* aStartOffset, uint32_t* aEndOffset) {
+ // Navigate backward from anchor accessible to find start offset.
+ for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+
+ // Stop on embedded accessible since embedded accessibles are combined into
+ // own range.
+ if (!currAcc->IsText()) break;
+
+ MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+ "Text accessible has to have an associated DOM element");
+
+ bool offsetFound = false;
+ for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
+ TextAttr* textAttr = aAttrArray[attrIdx];
+ if (!textAttr->Equal(currAcc)) {
+ offsetFound = true;
+ break;
+ }
+ }
+
+ if (offsetFound) break;
+
+ *(aStartOffset) -= nsAccUtils::TextLength(currAcc);
+ }
+
+ // Navigate forward from anchor accessible to find end offset.
+ uint32_t childLen = mHyperTextAcc->ChildCount();
+ for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childLen; childIdx++) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (!currAcc->IsText()) break;
+
+ MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+ "Text accessible has to have an associated DOM element");
+
+ bool offsetFound = false;
+ for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
+ TextAttr* textAttr = aAttrArray[attrIdx];
+
+ // Alter the end offset when text attribute changes its value and stop
+ // the search.
+ if (!textAttr->Equal(currAcc)) {
+ offsetFound = true;
+ break;
+ }
+ }
+
+ if (offsetFound) break;
+
+ (*aEndOffset) += nsAccUtils::TextLength(currAcc);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LangTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::LangTextAttr::LangTextAttr(HyperTextAccessible* aRoot,
+ nsIContent* aRootElm, nsIContent* aElm)
+ : TTextAttr<nsString>(!aElm), mRootContent(aRootElm) {
+ aRoot->Language(mRootNativeValue);
+ mIsRootDefined = !mRootNativeValue.IsEmpty();
+
+ if (aElm) {
+ nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue);
+ mIsDefined = !mNativeValue.IsEmpty();
+ }
+}
+
+TextAttrsMgr::LangTextAttr::~LangTextAttr() {}
+
+bool TextAttrsMgr::LangTextAttr::GetValueFor(Accessible* aAccessible,
+ nsString* aValue) {
+ nsCoreUtils::GetLanguageFor(aAccessible->GetContent(), mRootContent, *aValue);
+ return !aValue->IsEmpty();
+}
+
+void TextAttrsMgr::LangTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const nsString& aValue) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::language, aValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// InvalidTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::InvalidTextAttr::InvalidTextAttr(nsIContent* aRootElm,
+ nsIContent* aElm)
+ : TTextAttr<uint32_t>(!aElm), mRootElm(aRootElm) {
+ mIsRootDefined = GetValue(mRootElm, &mRootNativeValue);
+ if (aElm) mIsDefined = GetValue(aElm, &mNativeValue);
+}
+
+bool TextAttrsMgr::InvalidTextAttr::GetValueFor(Accessible* aAccessible,
+ uint32_t* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ return elm ? GetValue(elm, aValue) : false;
+}
+
+void TextAttrsMgr::InvalidTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const uint32_t& aValue) {
+ switch (aValue) {
+ case eFalse:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, u"false"_ns);
+ break;
+
+ case eGrammar:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, u"grammar"_ns);
+ break;
+
+ case eSpelling:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, u"spelling"_ns);
+ break;
+
+ case eTrue:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, u"true"_ns);
+ break;
+ }
+}
+
+bool TextAttrsMgr::InvalidTextAttr::GetValue(nsIContent* aElm,
+ uint32_t* aValue) {
+ nsIContent* elm = aElm;
+ do {
+ if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) {
+ static dom::Element::AttrValuesArray tokens[] = {
+ nsGkAtoms::_false, nsGkAtoms::grammar, nsGkAtoms::spelling, nullptr};
+
+ int32_t idx = elm->AsElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::aria_invalid, tokens, eCaseMatters);
+ switch (idx) {
+ case 0:
+ *aValue = eFalse;
+ return true;
+ case 1:
+ *aValue = eGrammar;
+ return true;
+ case 2:
+ *aValue = eSpelling;
+ return true;
+ default:
+ *aValue = eTrue;
+ return true;
+ }
+ }
+ } while ((elm = elm->GetParent()) && elm != mRootElm);
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BGColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::BGColorTextAttr::BGColorTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<nscolor>(!aFrame), mRootFrame(aRootFrame) {
+ mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
+ if (aFrame) mIsDefined = GetColor(aFrame, &mNativeValue);
+}
+
+bool TextAttrsMgr::BGColorTextAttr::GetValueFor(Accessible* aAccessible,
+ nscolor* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ return GetColor(frame, aValue);
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::BGColorTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const nscolor& aValue) {
+ nsAutoString formattedValue;
+ StyleInfo::FormatColor(aValue, formattedValue);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::backgroundColor,
+ formattedValue);
+}
+
+bool TextAttrsMgr::BGColorTextAttr::GetColor(nsIFrame* aFrame,
+ nscolor* aColor) {
+ nscolor backgroundColor = aFrame->StyleBackground()->BackgroundColor(aFrame);
+ if (NS_GET_A(backgroundColor) > 0) {
+ *aColor = backgroundColor;
+ return true;
+ }
+
+ nsContainerFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame) {
+ *aColor = aFrame->PresContext()->DefaultBackgroundColor();
+ return true;
+ }
+
+ // Each frame of parents chain for the initially passed 'aFrame' has
+ // transparent background color. So background color isn't changed from
+ // 'mRootFrame' to initially passed 'aFrame'.
+ if (parentFrame == mRootFrame) return false;
+
+ return GetColor(parentFrame, aColor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::ColorTextAttr::ColorTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<nscolor>(!aFrame) {
+ mRootNativeValue = aRootFrame->StyleText()->mColor.ToColor();
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleText()->mColor.ToColor();
+ mIsDefined = true;
+ }
+}
+
+bool TextAttrsMgr::ColorTextAttr::GetValueFor(Accessible* aAccessible,
+ nscolor* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ if (nsIFrame* frame = elm->GetPrimaryFrame()) {
+ *aValue = frame->StyleText()->mColor.ToColor();
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::ColorTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const nscolor& aValue) {
+ nsAutoString formattedValue;
+ StyleInfo::FormatColor(aValue, formattedValue);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::color, formattedValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FontFamilyTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<nsString>(!aFrame) {
+ mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
+
+ if (aFrame) mIsDefined = GetFontFamily(aFrame, mNativeValue);
+}
+
+bool TextAttrsMgr::FontFamilyTextAttr::GetValueFor(Accessible* aAccessible,
+ nsString* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ return GetFontFamily(frame, *aValue);
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::FontFamilyTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const nsString& aValue) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_family, aValue);
+}
+
+bool TextAttrsMgr::FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame,
+ nsString& aFamily) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
+
+ gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+ gfxFont* font = fontGroup->GetFirstValidFont();
+ gfxFontEntry* fontEntry = font->GetFontEntry();
+ aFamily.Append(NS_ConvertUTF8toUTF16(fontEntry->FamilyName()));
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FontSizeTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontSizeTextAttr::FontSizeTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<nscoord>(!aFrame) {
+ mDC = aRootFrame->PresContext()->DeviceContext();
+
+ mRootNativeValue = aRootFrame->StyleFont()->mSize.ToAppUnits();
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mSize.ToAppUnits();
+ mIsDefined = true;
+ }
+}
+
+bool TextAttrsMgr::FontSizeTextAttr::GetValueFor(Accessible* aAccessible,
+ nscoord* aValue) {
+ nsIContent* el = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (el) {
+ nsIFrame* frame = el->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleFont()->mSize.ToAppUnits();
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::FontSizeTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const nscoord& aValue) {
+ // Convert from nscoord to pt.
+ //
+ // Note: according to IA2, "The conversion doesn't have to be exact.
+ // The intent is to give the user a feel for the size of the text."
+ //
+ // ATK does not specify a unit and will likely follow IA2 here.
+ //
+ // XXX todo: consider sharing this code with layout module? (bug 474621)
+ float px = NSAppUnitsToFloatPixels(aValue, mozilla::AppUnitsPerCSSPixel());
+ // Each pt is 4/3 of a CSS pixel.
+ int pts = NS_lround(px * 3 / 4);
+
+ nsAutoString value;
+ value.AppendInt(pts);
+ value.AppendLiteral("pt");
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_size, value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FontStyleTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontStyleTextAttr::FontStyleTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<FontSlantStyle>(!aFrame) {
+ mRootNativeValue = aRootFrame->StyleFont()->mFont.style;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mFont.style;
+ mIsDefined = true;
+ }
+}
+
+bool TextAttrsMgr::FontStyleTextAttr::GetValueFor(Accessible* aAccessible,
+ FontSlantStyle* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleFont()->mFont.style;
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::FontStyleTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const FontSlantStyle& aValue) {
+ nsAutoString string;
+ nsStyleUtil::AppendFontSlantStyle(aValue, string);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_style, string);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FontWeightTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontWeightTextAttr::FontWeightTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<FontWeight>(!aFrame) {
+ mRootNativeValue = GetFontWeight(aRootFrame);
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = GetFontWeight(aFrame);
+ mIsDefined = true;
+ }
+}
+
+bool TextAttrsMgr::FontWeightTextAttr::GetValueFor(Accessible* aAccessible,
+ FontWeight* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = GetFontWeight(frame);
+ return true;
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::FontWeightTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const FontWeight& aValue) {
+ nsAutoString formattedValue;
+ formattedValue.AppendFloat(aValue.ToFloat());
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::fontWeight, formattedValue);
+}
+
+FontWeight TextAttrsMgr::FontWeightTextAttr::GetFontWeight(nsIFrame* aFrame) {
+ // nsFont::width isn't suitable here because it's necessary to expose real
+ // value of font weight (used font might not have some font weight values).
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
+
+ gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+ gfxFont* font = fontGroup->GetFirstValidFont();
+
+ // When there doesn't exist a bold font in the family and so the rendering of
+ // a non-bold font face is changed so that the user sees what looks like a
+ // bold font, i.e. synthetic bolding is used. IsSyntheticBold method is only
+ // needed on Mac, but it is "safe" to use on all platforms. (For non-Mac
+ // platforms it always return false.)
+ if (font->IsSyntheticBold()) {
+ return FontWeight::Bold();
+ }
+
+ // On Windows, font->GetStyle()->weight will give the same weight as
+ // fontEntry->Weight(), the weight of the first font in the font group,
+ // which may not be the weight of the font face used to render the
+ // characters. On Mac, font->GetStyle()->weight will just give the same
+ // number as getComputedStyle(). fontEntry->Weight() will give the weight
+ // range supported by the font face used, so we clamp the weight that was
+ // requested by style to what is actually supported by the font.
+ gfxFontEntry* fontEntry = font->GetFontEntry();
+ return fontEntry->Weight().Clamp(font->GetStyle()->weight);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AutoGeneratedTextAttr
+////////////////////////////////////////////////////////////////////////////////
+TextAttrsMgr::AutoGeneratedTextAttr::AutoGeneratedTextAttr(
+ HyperTextAccessible* aHyperTextAcc, Accessible* aAccessible)
+ : TTextAttr<bool>(!aAccessible) {
+ mRootNativeValue = false;
+ mIsRootDefined = false;
+
+ if (aAccessible)
+ mIsDefined = mNativeValue =
+ ((aAccessible->NativeRole() == roles::STATICTEXT) ||
+ (aAccessible->NativeRole() == roles::LISTITEM_MARKER));
+}
+
+bool TextAttrsMgr::AutoGeneratedTextAttr::GetValueFor(Accessible* aAccessible,
+ bool* aValue) {
+ return *aValue = (aAccessible->NativeRole() == roles::STATICTEXT);
+}
+
+void TextAttrsMgr::AutoGeneratedTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const bool& aValue) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::auto_generated,
+ aValue ? u"true"_ns : u"false"_ns);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextDecorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::TextDecorValue::TextDecorValue(nsIFrame* aFrame) {
+ const nsStyleTextReset* textReset = aFrame->StyleTextReset();
+ mStyle = textReset->mTextDecorationStyle;
+ mColor = textReset->mTextDecorationColor.CalcColor(aFrame);
+ mLine =
+ textReset->mTextDecorationLine & (StyleTextDecorationLine::UNDERLINE |
+ StyleTextDecorationLine::LINE_THROUGH);
+}
+
+TextAttrsMgr::TextDecorTextAttr::TextDecorTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<TextDecorValue>(!aFrame) {
+ mRootNativeValue = TextDecorValue(aRootFrame);
+ mIsRootDefined = mRootNativeValue.IsDefined();
+
+ if (aFrame) {
+ mNativeValue = TextDecorValue(aFrame);
+ mIsDefined = mNativeValue.IsDefined();
+ }
+}
+
+bool TextAttrsMgr::TextDecorTextAttr::GetValueFor(Accessible* aAccessible,
+ TextDecorValue* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = TextDecorValue(frame);
+ return aValue->IsDefined();
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::TextDecorTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const TextDecorValue& aValue) {
+ if (aValue.IsUnderline()) {
+ nsAutoString formattedStyle;
+ StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textUnderlineStyle,
+ formattedStyle);
+
+ nsAutoString formattedColor;
+ StyleInfo::FormatColor(aValue.Color(), formattedColor);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textUnderlineColor,
+ formattedColor);
+ return;
+ }
+
+ if (aValue.IsLineThrough()) {
+ nsAutoString formattedStyle;
+ StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textLineThroughStyle,
+ formattedStyle);
+
+ nsAutoString formattedColor;
+ StyleInfo::FormatColor(aValue.Color(), formattedColor);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textLineThroughColor,
+ formattedColor);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextPosTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::TextPosTextAttr::TextPosTextAttr(nsIFrame* aRootFrame,
+ nsIFrame* aFrame)
+ : TTextAttr<TextPosValue>(!aFrame) {
+ mRootNativeValue = GetTextPosValue(aRootFrame);
+ mIsRootDefined = mRootNativeValue != eTextPosNone;
+
+ if (aFrame) {
+ mNativeValue = GetTextPosValue(aFrame);
+ mIsDefined = mNativeValue != eTextPosNone;
+ }
+}
+
+bool TextAttrsMgr::TextPosTextAttr::GetValueFor(Accessible* aAccessible,
+ TextPosValue* aValue) {
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = GetTextPosValue(frame);
+ return *aValue != eTextPosNone;
+ }
+ }
+ return false;
+}
+
+void TextAttrsMgr::TextPosTextAttr::ExposeValue(
+ nsIPersistentProperties* aAttributes, const TextPosValue& aValue) {
+ switch (aValue) {
+ case eTextPosBaseline:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ u"baseline"_ns);
+ break;
+
+ case eTextPosSub:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition, u"sub"_ns);
+ break;
+
+ case eTextPosSuper:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition, u"super"_ns);
+ break;
+
+ case eTextPosNone:
+ break;
+ }
+}
+
+TextAttrsMgr::TextPosValue TextAttrsMgr::TextPosTextAttr::GetTextPosValue(
+ nsIFrame* aFrame) const {
+ const auto& verticalAlign = aFrame->StyleDisplay()->mVerticalAlign;
+ if (verticalAlign.IsKeyword()) {
+ switch (verticalAlign.AsKeyword()) {
+ case StyleVerticalAlignKeyword::Baseline:
+ return eTextPosBaseline;
+ case StyleVerticalAlignKeyword::Sub:
+ return eTextPosSub;
+ case StyleVerticalAlignKeyword::Super:
+ return eTextPosSuper;
+ // No good guess for the rest, so do not expose value of text-position
+ // attribute.
+ default:
+ return eTextPosNone;
+ }
+ }
+
+ const auto& length = verticalAlign.AsLength();
+ if (length.ConvertsToPercentage()) {
+ float percentValue = length.ToPercentage();
+ return percentValue > 0
+ ? eTextPosSuper
+ : (percentValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ if (length.ConvertsToLength()) {
+ nscoord coordValue = length.ToLength();
+ return coordValue > 0 ? eTextPosSuper
+ : (coordValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ if (const nsIContent* content = aFrame->GetContent()) {
+ if (content->IsHTMLElement(nsGkAtoms::sup)) return eTextPosSuper;
+ if (content->IsHTMLElement(nsGkAtoms::sub)) return eTextPosSub;
+ }
+
+ return eTextPosNone;
+}
diff --git a/accessible/base/TextAttrs.h b/accessible/base/TextAttrs.h
new file mode 100644
index 0000000000..66321c944b
--- /dev/null
+++ b/accessible/base/TextAttrs.h
@@ -0,0 +1,433 @@
+/* -*- 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 nsTextAttrs_h_
+#define nsTextAttrs_h_
+
+#include "mozilla/FontPropertyTypes.h"
+#include "nsCOMPtr.h"
+#include "nsColor.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+
+class nsIFrame;
+class nsIPersistentProperties;
+class nsIContent;
+class nsDeviceContext;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+
+/**
+ * Used to expose text attributes for the hyper text accessible (see
+ * HyperTextAccessible class).
+ *
+ * @note "invalid: spelling" text attribute is implemented entirely in
+ * HyperTextAccessible class.
+ */
+class TextAttrsMgr {
+ public:
+ /**
+ * Constructor. Used to expose default text attributes.
+ */
+ explicit TextAttrsMgr(HyperTextAccessible* aHyperTextAcc)
+ : mOffsetAcc(nullptr),
+ mHyperTextAcc(aHyperTextAcc),
+ mOffsetAccIdx(-1),
+ mIncludeDefAttrs(true) {}
+
+ /**
+ * Constructor. Used to expose text attributes at the given offset.
+ *
+ * @param aHyperTextAcc [in] hyper text accessible text attributes are
+ * calculated for
+ * @param aIncludeDefAttrs [optional] indicates whether default text
+ * attributes should be included into list of exposed
+ * text attributes
+ * @param oOffsetAcc [optional] offset an accessible the text attributes
+ * should be calculated for
+ * @param oOffsetAccIdx [optional] index in parent of offset accessible
+ */
+ TextAttrsMgr(HyperTextAccessible* aHyperTextAcc, bool aIncludeDefAttrs,
+ Accessible* aOffsetAcc, int32_t aOffsetAccIdx)
+ : mOffsetAcc(aOffsetAcc),
+ mHyperTextAcc(aHyperTextAcc),
+ mOffsetAccIdx(aOffsetAccIdx),
+ mIncludeDefAttrs(aIncludeDefAttrs) {}
+
+ /*
+ * Return text attributes and hyper text offsets where these attributes are
+ * applied. Offsets are calculated in the case of non default attributes.
+ *
+ * @note In the case of default attributes pointers on hyper text offsets
+ * must be skipped.
+ *
+ * @param aAttributes [in, out] text attributes list
+ * @param aStartHTOffset [out, optional] start hyper text offset
+ * @param aEndHTOffset [out, optional] end hyper text offset
+ */
+ void GetAttributes(nsIPersistentProperties* aAttributes,
+ uint32_t* aStartHTOffset = nullptr,
+ uint32_t* aEndHTOffset = nullptr);
+
+ protected:
+ /**
+ * Calculates range (start and end offsets) of text where the text attributes
+ * are stretched. New offsets may be smaller if one of text attributes changes
+ * its value before or after the given offsets.
+ *
+ * @param aTextAttrArray [in] text attributes array
+ * @param aAttrArrayLen [in] text attributes array length
+ * @param aStartHTOffset [in, out] the start offset
+ * @param aEndHTOffset [in, out] the end offset
+ */
+ class TextAttr;
+ void GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
+ uint32_t* aStartOffset, uint32_t* aEndOffset);
+
+ private:
+ Accessible* mOffsetAcc;
+ HyperTextAccessible* mHyperTextAcc;
+ int32_t mOffsetAccIdx;
+ bool mIncludeDefAttrs;
+
+ protected:
+ /**
+ * Interface class of text attribute class implementations.
+ */
+ class TextAttr {
+ public:
+ /**
+ * Expose the text attribute to the given attribute set.
+ *
+ * @param aAttributes [in] the given attribute set
+ * @param aIncludeDefAttrValue [in] if true then attribute is exposed even
+ * if its value is the same as default one
+ */
+ virtual void Expose(nsIPersistentProperties* aAttributes,
+ bool aIncludeDefAttrValue) = 0;
+
+ /**
+ * Return true if the text attribute value on the given element equals with
+ * predefined attribute value.
+ */
+ virtual bool Equal(Accessible* aAccessible) = 0;
+ };
+
+ /**
+ * Base class to work with text attributes. See derived classes below.
+ */
+ template <class T>
+ class TTextAttr : public TextAttr {
+ public:
+ explicit TTextAttr(bool aGetRootValue) : mGetRootValue(aGetRootValue) {}
+
+ // TextAttr
+ virtual void Expose(nsIPersistentProperties* aAttributes,
+ bool aIncludeDefAttrValue) override {
+ if (mGetRootValue) {
+ if (mIsRootDefined) ExposeValue(aAttributes, mRootNativeValue);
+ return;
+ }
+
+ if (mIsDefined) {
+ if (aIncludeDefAttrValue || mRootNativeValue != mNativeValue)
+ ExposeValue(aAttributes, mNativeValue);
+ return;
+ }
+
+ if (aIncludeDefAttrValue && mIsRootDefined)
+ ExposeValue(aAttributes, mRootNativeValue);
+ }
+
+ virtual bool Equal(Accessible* aAccessible) override {
+ T nativeValue;
+ bool isDefined = GetValueFor(aAccessible, &nativeValue);
+
+ if (!mIsDefined && !isDefined) return true;
+
+ if (mIsDefined && isDefined) return nativeValue == mNativeValue;
+
+ if (mIsDefined) return mNativeValue == mRootNativeValue;
+
+ return nativeValue == mRootNativeValue;
+ }
+
+ protected:
+ // Expose the text attribute with the given value to attribute set.
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const T& aValue) = 0;
+
+ // Return native value for the given DOM element.
+ virtual bool GetValueFor(Accessible* aAccessible, T* aValue) = 0;
+
+ // Indicates if root value should be exposed.
+ bool mGetRootValue;
+
+ // Native value and flag indicating if the value is defined (initialized in
+ // derived classes). Note, undefined native value means it is inherited
+ // from root.
+ MOZ_INIT_OUTSIDE_CTOR T mNativeValue;
+ MOZ_INIT_OUTSIDE_CTOR bool mIsDefined;
+
+ // Native root value and flag indicating if the value is defined
+ // (initialized in derived classes).
+ MOZ_INIT_OUTSIDE_CTOR T mRootNativeValue;
+ MOZ_INIT_OUTSIDE_CTOR bool mIsRootDefined;
+ };
+
+ /**
+ * Class is used for the work with 'language' text attribute.
+ */
+ class LangTextAttr : public TTextAttr<nsString> {
+ public:
+ LangTextAttr(HyperTextAccessible* aRoot, nsIContent* aRootElm,
+ nsIContent* aElm);
+ virtual ~LangTextAttr();
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ nsString* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nsString& aValue) override;
+
+ private:
+ nsCOMPtr<nsIContent> mRootContent;
+ };
+
+ /**
+ * Class is used for the 'invalid' text attribute. Note, it calculated
+ * the attribute from aria-invalid attribute only; invalid:spelling attribute
+ * calculated from misspelled text in the editor is managed by
+ * HyperTextAccessible and applied on top of the value from aria-invalid.
+ */
+ class InvalidTextAttr : public TTextAttr<uint32_t> {
+ public:
+ InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm);
+ virtual ~InvalidTextAttr(){};
+
+ protected:
+ enum { eFalse, eGrammar, eSpelling, eTrue };
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ uint32_t* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const uint32_t& aValue) override;
+
+ private:
+ bool GetValue(nsIContent* aElm, uint32_t* aValue);
+ nsIContent* mRootElm;
+ };
+
+ /**
+ * Class is used for the work with 'background-color' text attribute.
+ */
+ class BGColorTextAttr : public TTextAttr<nscolor> {
+ public:
+ BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~BGColorTextAttr() {}
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscolor& aValue) override;
+
+ private:
+ bool GetColor(nsIFrame* aFrame, nscolor* aColor);
+ nsIFrame* mRootFrame;
+ };
+
+ /**
+ * Class is used for the work with 'color' text attribute.
+ */
+ class ColorTextAttr : public TTextAttr<nscolor> {
+ public:
+ ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~ColorTextAttr() {}
+
+ protected:
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscolor& aValue) override;
+ };
+
+ /**
+ * Class is used for the work with "font-family" text attribute.
+ */
+ class FontFamilyTextAttr : public TTextAttr<nsString> {
+ public:
+ FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontFamilyTextAttr() {}
+
+ protected:
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ nsString* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nsString& aValue) override;
+
+ private:
+ bool GetFontFamily(nsIFrame* aFrame, nsString& aFamily);
+ };
+
+ /**
+ * Class is used for the work with "font-size" text attribute.
+ */
+ class FontSizeTextAttr : public TTextAttr<nscoord> {
+ public:
+ FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontSizeTextAttr() {}
+
+ protected:
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscoord* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscoord& aValue) override;
+
+ private:
+ nsDeviceContext* mDC;
+ };
+
+ /**
+ * Class is used for the work with "font-style" text attribute.
+ */
+ class FontStyleTextAttr : public TTextAttr<mozilla::FontSlantStyle> {
+ public:
+ FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontStyleTextAttr() {}
+
+ protected:
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aContent,
+ mozilla::FontSlantStyle* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const mozilla::FontSlantStyle& aValue) override;
+ };
+
+ /**
+ * Class is used for the work with "font-weight" text attribute.
+ */
+ class FontWeightTextAttr : public TTextAttr<mozilla::FontWeight> {
+ public:
+ FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontWeightTextAttr() {}
+
+ protected:
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ mozilla::FontWeight* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const mozilla::FontWeight& aValue) override;
+
+ private:
+ mozilla::FontWeight GetFontWeight(nsIFrame* aFrame);
+ };
+
+ /**
+ * Class is used for the work with 'auto-generated' text attribute.
+ */
+ class AutoGeneratedTextAttr : public TTextAttr<bool> {
+ public:
+ AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
+ Accessible* aAccessible);
+ virtual ~AutoGeneratedTextAttr() {}
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, bool* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const bool& aValue) override;
+ };
+
+ /**
+ * TextDecorTextAttr class is used for the work with
+ * "text-line-through-style", "text-line-through-color",
+ * "text-underline-style" and "text-underline-color" text attributes.
+ */
+
+ class TextDecorValue {
+ public:
+ TextDecorValue()
+ : mColor{0},
+ mLine{StyleTextDecorationLine::NONE},
+ mStyle{NS_STYLE_TEXT_DECORATION_STYLE_NONE} {}
+ explicit TextDecorValue(nsIFrame* aFrame);
+
+ nscolor Color() const { return mColor; }
+ uint8_t Style() const { return mStyle; }
+
+ bool IsDefined() const { return IsUnderline() || IsLineThrough(); }
+ bool IsUnderline() const {
+ return bool(mLine & mozilla::StyleTextDecorationLine::UNDERLINE);
+ }
+ bool IsLineThrough() const {
+ return bool(mLine & mozilla::StyleTextDecorationLine::LINE_THROUGH);
+ }
+
+ bool operator==(const TextDecorValue& aValue) {
+ return mColor == aValue.mColor && mLine == aValue.mLine &&
+ mStyle == aValue.mStyle;
+ }
+ bool operator!=(const TextDecorValue& aValue) { return !(*this == aValue); }
+
+ private:
+ nscolor mColor;
+ mozilla::StyleTextDecorationLine mLine;
+ uint8_t mStyle;
+ };
+
+ class TextDecorTextAttr : public TTextAttr<TextDecorValue> {
+ public:
+ TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~TextDecorTextAttr() {}
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ TextDecorValue* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const TextDecorValue& aValue) override;
+ };
+
+ /**
+ * Class is used for the work with "text-position" text attribute.
+ */
+
+ enum TextPosValue {
+ eTextPosNone = 0,
+ eTextPosBaseline,
+ eTextPosSub,
+ eTextPosSuper
+ };
+
+ class TextPosTextAttr : public TTextAttr<TextPosValue> {
+ public:
+ TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~TextPosTextAttr() {}
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible,
+ TextPosValue* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const TextPosValue& aValue) override;
+
+ private:
+ TextPosValue GetTextPosValue(nsIFrame* aFrame) const;
+ };
+
+}; // TextAttrMgr
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextRange-inl.h b/accessible/base/TextRange-inl.h
new file mode 100644
index 0000000000..af6761bc0d
--- /dev/null
+++ b/accessible/base/TextRange-inl.h
@@ -0,0 +1,26 @@
+/* -*- 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_TextRange_inl_h__
+#define mozilla_a11y_TextRange_inl_h__
+
+#include "TextRange.h"
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline Accessible* TextRange::Container() const {
+ uint32_t pos1 = 0, pos2 = 0;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ return CommonParent(mStartContainer, mEndContainer, &parents1, &pos1,
+ &parents2, &pos2);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextRange.cpp b/accessible/base/TextRange.cpp
new file mode 100644
index 0000000000..679e46c6ee
--- /dev/null
+++ b/accessible/base/TextRange.cpp
@@ -0,0 +1,518 @@
+/* -*- 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 "TextRange-inl.h"
+
+#include "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "mozilla/dom/Selection.h"
+#include "nsAccUtils.h"
+
+namespace mozilla {
+namespace a11y {
+
+////////////////////////////////////////////////////////////////////////////////
+// TextPoint
+
+bool TextPoint::operator<(const TextPoint& aPoint) const {
+ if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
+
+ // Build the chain of parents
+ Accessible* p1 = mContainer;
+ Accessible* p2 = aPoint.mContainer;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(p1);
+ p1 = p1->Parent();
+ } while (p1);
+ do {
+ parents2.AppendElement(p2);
+ p2 = p2->Parent();
+ } while (p2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
+ for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+ Accessible* child1 = parents1.ElementAt(--pos1);
+ Accessible* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2)
+ return child1->IndexInParent() < child2->IndexInParent();
+ }
+
+ if (pos1 != 0) {
+ // If parents1 is a superset of parents2 then mContainer is a
+ // descendant of aPoint.mContainer. The next element down in parents1
+ // is mContainer's ancestor that is the child of aPoint.mContainer.
+ // We compare its end offset in aPoint.mContainer with aPoint.mOffset.
+ Accessible* child = parents1.ElementAt(pos1 - 1);
+ MOZ_ASSERT(child->Parent() == aPoint.mContainer);
+ return child->EndOffset() < static_cast<uint32_t>(aPoint.mOffset);
+ }
+
+ if (pos2 != 0) {
+ // If parents2 is a superset of parents1 then aPoint.mContainer is a
+ // descendant of mContainer. The next element down in parents2
+ // is aPoint.mContainer's ancestor that is the child of mContainer.
+ // We compare its start offset in mContainer with mOffset.
+ Accessible* child = parents2.ElementAt(pos2 - 1);
+ MOZ_ASSERT(child->Parent() == mContainer);
+ return static_cast<uint32_t>(mOffset) < child->StartOffset();
+ }
+
+ NS_ERROR("Broken tree?!");
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextRange
+
+TextRange::TextRange(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset)
+ : mRoot(aRoot),
+ mStartContainer(aStartContainer),
+ mEndContainer(aEndContainer),
+ mStartOffset(aStartOffset),
+ mEndOffset(aEndOffset) {}
+
+void TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const {
+ if (mStartContainer == mEndContainer) {
+ int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset);
+ int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset);
+ for (int32_t idx = startIdx; idx <= endIdx; idx++) {
+ Accessible* child = mStartContainer->GetChildAt(idx);
+ if (!child->IsText()) {
+ aChildren->AppendElement(child);
+ }
+ }
+ return;
+ }
+
+ Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset);
+ Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset);
+
+ uint32_t pos1 = 0, pos2 = 0;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ Accessible* container =
+ CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2);
+
+ // Traverse the tree up to the container and collect embedded objects.
+ for (uint32_t idx = 0; idx < pos1 - 1; idx++) {
+ Accessible* parent = parents1[idx + 1];
+ Accessible* child = parents1[idx];
+ uint32_t childCount = parent->ChildCount();
+ for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount;
+ childIdx++) {
+ Accessible* next = parent->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+ }
+
+ // Traverse through direct children in the container.
+ int32_t endIdx = parents2[pos2 - 1]->IndexInParent();
+ int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1;
+ for (; childIdx < endIdx; childIdx++) {
+ Accessible* next = container->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+
+ // Traverse down from the container to end point.
+ for (int32_t idx = pos2 - 2; idx > 0; idx--) {
+ Accessible* parent = parents2[idx];
+ Accessible* child = parents2[idx - 1];
+ int32_t endIdx = child->IndexInParent();
+ for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) {
+ Accessible* next = parent->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+ }
+}
+
+void TextRange::Text(nsAString& aText) const {
+ Accessible* current = mStartContainer->GetChildAtOffset(mStartOffset);
+ uint32_t startIntlOffset =
+ mStartOffset - mStartContainer->GetChildOffset(current);
+
+ while (current && TextInternal(aText, current, startIntlOffset)) {
+ current = current->Parent();
+ if (!current) break;
+
+ current = current->NextSibling();
+ }
+}
+
+void TextRange::Bounds(nsTArray<nsIntRect> aRects) const {}
+
+void TextRange::Normalize(ETextUnit aUnit) {}
+
+bool TextRange::Crop(Accessible* aContainer) {
+ uint32_t boundaryPos = 0, containerPos = 0;
+ AutoTArray<Accessible*, 30> boundaryParents, containerParents;
+
+ // Crop the start boundary.
+ Accessible* container = nullptr;
+ Accessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset);
+ if (boundary != aContainer) {
+ CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+ &containerParents, &containerPos);
+
+ if (boundaryPos == 0) {
+ if (containerPos != 0) {
+ // The container is contained by the start boundary, reduce the range to
+ // the point starting at the container.
+ aContainer->ToTextPoint(mStartContainer.StartAssignment(),
+ &mStartOffset);
+ static_cast<Accessible*>(mStartContainer)->AddRef();
+ } else {
+ // The start boundary and the container are siblings.
+ container = aContainer;
+ }
+ } else if (containerPos != 0) {
+ // The container does not contain the start boundary.
+ boundary = boundaryParents[boundaryPos];
+ container = containerParents[containerPos];
+ }
+
+ if (container) {
+ // If the range start is after the container, then make the range invalid.
+ if (boundary->IndexInParent() > container->IndexInParent()) {
+ return !!(mRoot = nullptr);
+ }
+
+ // If the range starts before the container, then reduce the range to
+ // the point starting at the container.
+ if (boundary->IndexInParent() < container->IndexInParent()) {
+ container->ToTextPoint(mStartContainer.StartAssignment(),
+ &mStartOffset);
+ mStartContainer.get()->AddRef();
+ }
+ }
+
+ boundaryParents.SetLengthAndRetainStorage(0);
+ containerParents.SetLengthAndRetainStorage(0);
+ }
+
+ boundary = mEndContainer->GetChildAtOffset(mEndOffset);
+ if (boundary == aContainer) {
+ return true;
+ }
+
+ // Crop the end boundary.
+ container = nullptr;
+ CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+ &containerParents, &containerPos);
+
+ if (boundaryPos == 0) {
+ if (containerPos != 0) {
+ aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset,
+ false);
+ static_cast<Accessible*>(mEndContainer)->AddRef();
+ } else {
+ container = aContainer;
+ }
+ } else if (containerPos != 0) {
+ boundary = boundaryParents[boundaryPos];
+ container = containerParents[containerPos];
+ }
+
+ if (!container) {
+ return true;
+ }
+
+ if (boundary->IndexInParent() < container->IndexInParent()) {
+ return !!(mRoot = nullptr);
+ }
+
+ if (boundary->IndexInParent() > container->IndexInParent()) {
+ container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
+ static_cast<Accessible*>(mEndContainer)->AddRef();
+ }
+
+ return true;
+}
+
+void TextRange::FindText(const nsAString& aText, EDirection aDirection,
+ nsCaseTreatment aCaseSensitive,
+ TextRange* aFoundRange) const {}
+
+void TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+ TextRange* aFoundRange) const {}
+
+void TextRange::AddToSelection() const {}
+
+void TextRange::RemoveFromSelection() const {}
+
+bool TextRange::SetSelectionAt(int32_t aSelectionNum) const {
+ RefPtr<dom::Selection> domSel = mRoot->DOMSelection();
+ if (!domSel) {
+ return false;
+ }
+
+ RefPtr<nsRange> range = nsRange::Create(mRoot->GetContent());
+ uint32_t rangeCount = domSel->RangeCount();
+ if (aSelectionNum == static_cast<int32_t>(rangeCount)) {
+ range = nsRange::Create(mRoot->GetContent());
+ } else {
+ range = domSel->GetRangeAt(aSelectionNum);
+ }
+
+ if (!range) {
+ return false;
+ }
+
+ bool reversed;
+ AssignDOMRange(range, &reversed);
+
+ // If this is not a new range, notify selection listeners that the existing
+ // selection range has changed. Otherwise, just add the new range.
+ if (aSelectionNum != static_cast<int32_t>(rangeCount)) {
+ domSel->RemoveRangeAndUnselectFramesAndNotifyListeners(*range,
+ IgnoreErrors());
+ }
+
+ IgnoredErrorResult err;
+ domSel->AddRangeAndSelectFramesAndNotifyListeners(*range, err);
+ if (!err.Failed()) {
+ // Changing the direction of the selection assures that the caret
+ // will be at the logical end of the selection.
+ domSel->SetDirection(reversed ? eDirPrevious : eDirNext);
+ return true;
+ }
+
+ return false;
+}
+
+void TextRange::ScrollIntoView(uint32_t aScrollType) const {
+ RefPtr<nsRange> range = nsRange::Create(mRoot->GetContent());
+ if (AssignDOMRange(range)) {
+ nsCoreUtils::ScrollSubstringTo(mStartContainer->GetFrame(), range,
+ aScrollType);
+ }
+}
+
+/**
+ * Convert the given DOM point to a DOM point in non-generated contents.
+ *
+ * If aDOMPoint is in ::before, the result is immediately after it.
+ * If aDOMPoint is in ::after, the result is immediately before it.
+ */
+static DOMPoint ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
+ nsIContent* aElementContent) {
+ MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
+
+ // ::before pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForBefore()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::before must have parent element");
+ // The first child of its parent (i.e., immediately after the ::before) is
+ // good point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(), 0);
+ }
+
+ // ::after pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForAfter()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::after must have parent element");
+ // The end of its parent (i.e., immediately before the ::after) is good
+ // point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(),
+ aElementContent->GetParent()->GetChildCount());
+ }
+
+ return aDOMPoint;
+}
+
+/**
+ * GetElementAsContentOf() returns a content representing an element which is
+ * or includes aNode.
+ *
+ * XXX This method is enough to retrieve ::before or ::after pseudo element.
+ * So, if you want to use this for other purpose, you might need to check
+ * ancestors too.
+ */
+static nsIContent* GetElementAsContentOf(nsINode* aNode) {
+ if (auto* element = dom::Element::FromNode(aNode)) {
+ return element;
+ }
+ return aNode->GetParentElement();
+}
+
+bool TextRange::AssignDOMRange(nsRange* aRange, bool* aReversed) const {
+ bool reversed = EndPoint() < StartPoint();
+ if (aReversed) {
+ *aReversed = reversed;
+ }
+
+ DOMPoint startPoint = reversed
+ ? mEndContainer->OffsetToDOMPoint(mEndOffset)
+ : mStartContainer->OffsetToDOMPoint(mStartOffset);
+ if (!startPoint.node) {
+ return false;
+ }
+
+ // HyperTextAccessible manages pseudo elements generated by ::before or
+ // ::after. However, contents of them are not in the DOM tree normally.
+ // Therefore, they are not selectable and editable. So, when this creates
+ // a DOM range, it should not start from nor end in any pseudo contents.
+
+ nsIContent* container = GetElementAsContentOf(startPoint.node);
+ DOMPoint startPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(startPoint, container);
+ aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
+
+ // If the caller wants collapsed range, let's collapse the range to its start.
+ if (mEndContainer == mStartContainer && mEndOffset == mStartOffset) {
+ aRange->Collapse(true);
+ return true;
+ }
+
+ DOMPoint endPoint = reversed ? mStartContainer->OffsetToDOMPoint(mStartOffset)
+ : mEndContainer->OffsetToDOMPoint(mEndOffset);
+ if (!endPoint.node) {
+ return false;
+ }
+
+ if (startPoint.node != endPoint.node) {
+ container = GetElementAsContentOf(endPoint.node);
+ }
+
+ DOMPoint endPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(endPoint, container);
+ aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
+ return true;
+}
+
+void TextRange::TextRangesFromSelection(dom::Selection* aSelection,
+ nsTArray<TextRange>* aRanges) {
+ MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
+
+ aRanges->SetCapacity(aSelection->RangeCount());
+
+ for (uint32_t idx = 0; idx < aSelection->RangeCount(); idx++) {
+ const nsRange* DOMRange = aSelection->GetRangeAt(idx);
+ HyperTextAccessible* startContainer =
+ nsAccUtils::GetTextContainer(DOMRange->GetStartContainer());
+ HyperTextAccessible* endContainer =
+ nsAccUtils::GetTextContainer(DOMRange->GetEndContainer());
+ HyperTextAccessible* commonAncestor = nsAccUtils::GetTextContainer(
+ DOMRange->GetClosestCommonInclusiveAncestor());
+ if (!startContainer || !endContainer) {
+ continue;
+ }
+
+ int32_t startOffset = startContainer->DOMPointToOffset(
+ DOMRange->GetStartContainer(), DOMRange->StartOffset(), false);
+ int32_t endOffset = endContainer->DOMPointToOffset(
+ DOMRange->GetEndContainer(), DOMRange->EndOffset(), true);
+
+ TextRange tr(commonAncestor && commonAncestor->IsTextField()
+ ? commonAncestor
+ : startContainer->Document(),
+ startContainer, startOffset, endContainer, endOffset);
+ *(aRanges->AppendElement()) = std::move(tr);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// pivate
+
+void TextRange::Set(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset) {
+ mRoot = aRoot;
+ mStartContainer = aStartContainer;
+ mEndContainer = aEndContainer;
+ mStartOffset = aStartOffset;
+ mEndOffset = aEndOffset;
+}
+
+bool TextRange::TextInternal(nsAString& aText, Accessible* aCurrent,
+ uint32_t aStartIntlOffset) const {
+ bool moveNext = true;
+ int32_t endIntlOffset = -1;
+ if (aCurrent->Parent() == mEndContainer &&
+ mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) {
+ uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent);
+ endIntlOffset = mEndOffset - currentStartOffset;
+ if (endIntlOffset == 0) return false;
+
+ moveNext = false;
+ }
+
+ if (aCurrent->IsTextLeaf()) {
+ aCurrent->AppendTextTo(aText, aStartIntlOffset,
+ endIntlOffset - aStartIntlOffset);
+ if (!moveNext) return false;
+ }
+
+ Accessible* next = aCurrent->FirstChild();
+ if (next) {
+ if (!TextInternal(aText, next, 0)) return false;
+ }
+
+ next = aCurrent->NextSibling();
+ if (next) {
+ if (!TextInternal(aText, next, 0)) return false;
+ }
+
+ return moveNext;
+}
+
+void TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
+ HyperTextAccessible& aContainer, int32_t aOffset,
+ HyperTextAccessible* aStopContainer,
+ int32_t aStopOffset) {}
+
+Accessible* TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+ nsTArray<Accessible*>* aParents1,
+ uint32_t* aPos1,
+ nsTArray<Accessible*>* aParents2,
+ uint32_t* aPos2) const {
+ if (aAcc1 == aAcc2) {
+ return aAcc1;
+ }
+
+ MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
+ "Wrong arguments");
+
+ // Build the chain of parents.
+ Accessible* p1 = aAcc1;
+ Accessible* p2 = aAcc2;
+ do {
+ aParents1->AppendElement(p1);
+ p1 = p1->Parent();
+ } while (p1);
+ do {
+ aParents2->AppendElement(p2);
+ p2 = p2->Parent();
+ } while (p2);
+
+ // Find where the parent chain differs
+ *aPos1 = aParents1->Length();
+ *aPos2 = aParents2->Length();
+ Accessible* parent = nullptr;
+ uint32_t len = 0;
+ for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
+ Accessible* child1 = aParents1->ElementAt(--(*aPos1));
+ Accessible* child2 = aParents2->ElementAt(--(*aPos2));
+ if (child1 != child2) break;
+
+ parent = child1;
+ }
+
+ return parent;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/TextRange.h b/accessible/base/TextRange.h
new file mode 100644
index 0000000000..20b1e78a86
--- /dev/null
+++ b/accessible/base/TextRange.h
@@ -0,0 +1,283 @@
+/* -*- 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_TextRange_h__
+#define mozilla_a11y_TextRange_h__
+
+#include <utility>
+
+#include "nsCaseTreatment.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+
+class nsIVariant;
+class nsRange;
+
+namespace mozilla {
+namespace dom {
+class Selection;
+} // namespace dom
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+
+/**
+ * A text point (hyper text + offset), represents a boundary of text range.
+ */
+struct TextPoint final {
+ TextPoint(HyperTextAccessible* aContainer, int32_t aOffset)
+ : mContainer(aContainer), mOffset(aOffset) {}
+ TextPoint(const TextPoint& aPoint)
+ : mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) {}
+
+ HyperTextAccessible* mContainer;
+ int32_t mOffset;
+
+ bool operator==(const TextPoint& aPoint) const {
+ return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset;
+ }
+ bool operator<(const TextPoint& aPoint) const;
+};
+
+/**
+ * Represents a text range within the text control or document.
+ */
+class TextRange final {
+ public:
+ TextRange(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
+ int32_t aStartOffset, HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset);
+ TextRange() : mStartOffset{0}, mEndOffset{0} {}
+ TextRange(TextRange&& aRange)
+ : mRoot(std::move(aRange.mRoot)),
+ mStartContainer(std::move(aRange.mStartContainer)),
+ mEndContainer(std::move(aRange.mEndContainer)),
+ mStartOffset(aRange.mStartOffset),
+ mEndOffset(aRange.mEndOffset) {}
+
+ TextRange& operator=(TextRange&& aRange) {
+ mRoot = std::move(aRange.mRoot);
+ mStartContainer = std::move(aRange.mStartContainer);
+ mEndContainer = std::move(aRange.mEndContainer);
+ mStartOffset = aRange.mStartOffset;
+ mEndOffset = aRange.mEndOffset;
+ return *this;
+ }
+
+ HyperTextAccessible* StartContainer() const { return mStartContainer; }
+ int32_t StartOffset() const { return mStartOffset; }
+ HyperTextAccessible* EndContainer() const { return mEndContainer; }
+ int32_t EndOffset() const { return mEndOffset; }
+
+ bool operator==(const TextRange& aRange) const {
+ return mStartContainer == aRange.mStartContainer &&
+ mStartOffset == aRange.mStartOffset &&
+ mEndContainer == aRange.mEndContainer &&
+ mEndOffset == aRange.mEndOffset;
+ }
+
+ TextPoint StartPoint() const {
+ return TextPoint(mStartContainer, mStartOffset);
+ }
+ TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); }
+
+ /**
+ * Return a container containing both start and end points.
+ */
+ Accessible* Container() const;
+
+ /**
+ * Return a list of embedded objects enclosed by the text range (includes
+ * partially overlapped objects).
+ */
+ void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;
+
+ /**
+ * Return text enclosed by the range.
+ */
+ void Text(nsAString& aText) const;
+
+ /**
+ * Return list of bounding rects of the text range by lines.
+ */
+ void Bounds(nsTArray<nsIntRect> aRects) const;
+
+ enum ETextUnit { eFormat, eWord, eLine, eParagraph, ePage, eDocument };
+
+ /**
+ * Move the range or its points on specified amount of given units.
+ */
+ void Move(ETextUnit aUnit, int32_t aCount) {
+ MoveEnd(aUnit, aCount);
+ MoveStart(aUnit, aCount);
+ }
+ void MoveStart(ETextUnit aUnit, int32_t aCount) {
+ MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset, mEndContainer,
+ mEndOffset);
+ }
+ void MoveEnd(ETextUnit aUnit, int32_t aCount) {
+ MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset);
+ }
+
+ /**
+ * Move the range points to the closest unit boundaries.
+ */
+ void Normalize(ETextUnit aUnit);
+
+ /**
+ * Crops the range if it overlaps the given accessible element boundaries,
+ * returns true if the range was cropped successfully.
+ */
+ bool Crop(Accessible* aContainer);
+
+ enum EDirection { eBackward, eForward };
+
+ /**
+ * Return range enclosing the found text.
+ */
+ void FindText(const nsAString& aText, EDirection aDirection,
+ nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;
+
+ enum EAttr {
+ eAnimationStyleAttr,
+ eAnnotationObjectsAttr,
+ eAnnotationTypesAttr,
+ eBackgroundColorAttr,
+ eBulletStyleAttr,
+ eCapStyleAttr,
+ eCaretBidiModeAttr,
+ eCaretPositionAttr,
+ eCultureAttr,
+ eFontNameAttr,
+ eFontSizeAttr,
+ eFontWeightAttr,
+ eForegroundColorAttr,
+ eHorizontalTextAlignmentAttr,
+ eIndentationFirstLineAttr,
+ eIndentationLeadingAttr,
+ eIndentationTrailingAttr,
+ eIsActiveAttr,
+ eIsHiddenAttr,
+ eIsItalicAttr,
+ eIsReadOnlyAttr,
+ eIsSubscriptAttr,
+ eIsSuperscriptAttr,
+ eLinkAttr,
+ eMarginBottomAttr,
+ eMarginLeadingAttr,
+ eMarginTopAttr,
+ eMarginTrailingAttr,
+ eOutlineStylesAttr,
+ eOverlineColorAttr,
+ eOverlineStyleAttr,
+ eSelectionActiveEndAttr,
+ eStrikethroughColorAttr,
+ eStrikethroughStyleAttr,
+ eStyleIdAttr,
+ eStyleNameAttr,
+ eTabsAttr,
+ eTextFlowDirectionsAttr,
+ eUnderlineColorAttr,
+ eUnderlineStyleAttr
+ };
+
+ /**
+ * Return range enclosing text having requested attribute.
+ */
+ void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+ TextRange* aFoundRange) const;
+
+ /**
+ * Add/remove the text range from selection.
+ */
+ void AddToSelection() const;
+ void RemoveFromSelection() const;
+ MOZ_CAN_RUN_SCRIPT bool SetSelectionAt(int32_t aSelectionNum) const;
+
+ /**
+ * Scroll the text range into view.
+ */
+ void ScrollIntoView(uint32_t aScrollType) const;
+
+ /**
+ * Convert stored hypertext offsets into DOM offsets and assign it to DOM
+ * range.
+ *
+ * Note that if start and/or end accessible offsets are in generated content
+ * such as ::before or
+ * ::after, the result range excludes the generated content. See also
+ * ClosestNotGeneratedDOMPoint() for more information.
+ *
+ * @param aRange [in, out] the range whose bounds to set
+ * @param aReversed [out] whether the start/end offsets were reversed.
+ * @return true if conversion was successful
+ */
+ bool AssignDOMRange(nsRange* aRange, bool* aReversed = nullptr) const;
+
+ /**
+ * Return true if this TextRange object represents an actual range of text.
+ */
+ bool IsValid() const { return mRoot; }
+
+ void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
+ mStartContainer = aContainer;
+ mStartOffset = aOffset;
+ }
+ void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset) {
+ mStartContainer = aContainer;
+ mStartOffset = aOffset;
+ }
+
+ static void TextRangesFromSelection(dom::Selection* aSelection,
+ nsTArray<TextRange>* aRanges);
+
+ private:
+ TextRange(const TextRange& aRange) = delete;
+ TextRange& operator=(const TextRange& aRange) = delete;
+
+ friend class HyperTextAccessible;
+ friend class xpcAccessibleTextRange;
+
+ void Set(HyperTextAccessible* aRoot, HyperTextAccessible* aStartContainer,
+ int32_t aStartOffset, HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset);
+
+ /**
+ * Text() method helper.
+ * @param aText [in,out] calculated text
+ * @param aCurrent [in] currently traversed node
+ * @param aStartIntlOffset [in] start offset if current node is a text node
+ * @return true if calculation is not finished yet
+ */
+ bool TextInternal(nsAString& aText, Accessible* aCurrent,
+ uint32_t aStartIntlOffset) const;
+
+ void MoveInternal(ETextUnit aUnit, int32_t aCount,
+ HyperTextAccessible& aContainer, int32_t aOffset,
+ HyperTextAccessible* aStopContainer = nullptr,
+ int32_t aStopOffset = 0);
+
+ /**
+ * A helper method returning a common parent for two given accessible
+ * elements.
+ */
+ Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+ nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
+ nsTArray<Accessible*>* aParents2,
+ uint32_t* aPos2) const;
+
+ RefPtr<HyperTextAccessible> mRoot;
+ RefPtr<HyperTextAccessible> mStartContainer;
+ RefPtr<HyperTextAccessible> mEndContainer;
+ int32_t mStartOffset;
+ int32_t mEndOffset;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextUpdater.cpp b/accessible/base/TextUpdater.cpp
new file mode 100644
index 0000000000..0ebe53d01b
--- /dev/null
+++ b/accessible/base/TextUpdater.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TextUpdater.h"
+
+#include "Accessible-inl.h"
+#include "DocAccessible-inl.h"
+#include "TextLeafAccessible.h"
+#include <algorithm>
+
+using namespace mozilla::a11y;
+
+void TextUpdater::Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
+ const nsAString& aNewText) {
+ NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
+
+ const nsString& oldText = aTextLeaf->Text();
+ uint32_t oldLen = oldText.Length(), newLen = aNewText.Length();
+ uint32_t minLen = std::min(oldLen, newLen);
+
+ // Skip coinciding begin substrings.
+ uint32_t skipStart = 0;
+ for (; skipStart < minLen; skipStart++) {
+ if (aNewText[skipStart] != oldText[skipStart]) break;
+ }
+
+ // The text was changed. Do update.
+ if (skipStart != minLen || oldLen != newLen) {
+ TextUpdater updater(aDocument, aTextLeaf);
+ updater.DoUpdate(aNewText, oldText, skipStart);
+ }
+}
+
+void TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
+ uint32_t aSkipStart) {
+ Accessible* parent = mTextLeaf->Parent();
+ if (!parent) return;
+
+ mHyperText = parent->AsHyperText();
+ if (!mHyperText) {
+ MOZ_ASSERT_UNREACHABLE("Text leaf parent is not hypertext!");
+ return;
+ }
+
+ // Get the text leaf accessible offset and invalidate cached offsets after it.
+ mTextOffset = mHyperText->GetChildOffset(mTextLeaf, true);
+ NS_ASSERTION(mTextOffset != -1, "Text leaf hasn't offset within hyper text!");
+
+ uint32_t oldLen = aOldText.Length(), newLen = aNewText.Length();
+ uint32_t minLen = std::min(oldLen, newLen);
+
+ // Trim coinciding substrings from the end.
+ uint32_t skipEnd = 0;
+ while (minLen - skipEnd > aSkipStart &&
+ aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
+ skipEnd++;
+ }
+
+ uint32_t strLen1 = oldLen - aSkipStart - skipEnd;
+ uint32_t strLen2 = newLen - aSkipStart - skipEnd;
+
+ const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
+ const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
+
+ // Increase offset of the text leaf on skipped characters amount.
+ mTextOffset += aSkipStart;
+
+ // It could be single insertion or removal or the case of long strings. Do not
+ // calculate the difference between long strings and prefer to fire pair of
+ // insert/remove events as the old string was replaced on the new one.
+ if (strLen1 == 0 || strLen2 == 0 || strLen1 > kMaxStrLen ||
+ strLen2 > kMaxStrLen) {
+ if (strLen1 > 0) {
+ // Fire text change event for removal.
+ RefPtr<AccEvent> textRemoveEvent =
+ new AccTextChangeEvent(mHyperText, mTextOffset, str1, false);
+ mDocument->FireDelayedEvent(textRemoveEvent);
+ }
+
+ if (strLen2 > 0) {
+ // Fire text change event for insertion.
+ RefPtr<AccEvent> textInsertEvent =
+ new AccTextChangeEvent(mHyperText, mTextOffset, str2, true);
+ mDocument->FireDelayedEvent(textInsertEvent);
+ }
+
+ mDocument->MaybeNotifyOfValueChange(mHyperText);
+
+ // Update the text.
+ mTextLeaf->SetText(aNewText);
+ return;
+ }
+
+ // Otherwise find the difference between strings and fire events.
+ // Note: we can skip initial and final coinciding characters since they don't
+ // affect the Levenshtein distance.
+
+ // Compute the flat structured matrix need to compute the difference.
+ uint32_t len1 = strLen1 + 1, len2 = strLen2 + 1;
+ uint32_t* entries = new uint32_t[len1 * len2];
+
+ for (uint32_t colIdx = 0; colIdx < len1; colIdx++) entries[colIdx] = colIdx;
+
+ uint32_t* row = entries;
+ for (uint32_t rowIdx = 1; rowIdx < len2; rowIdx++) {
+ uint32_t* prevRow = row;
+ row += len1;
+ row[0] = rowIdx;
+ for (uint32_t colIdx = 1; colIdx < len1; colIdx++) {
+ if (str1[colIdx - 1] != str2[rowIdx - 1]) {
+ uint32_t left = row[colIdx - 1];
+ uint32_t up = prevRow[colIdx];
+ uint32_t upleft = prevRow[colIdx - 1];
+ row[colIdx] = std::min(upleft, std::min(left, up)) + 1;
+ } else {
+ row[colIdx] = prevRow[colIdx - 1];
+ }
+ }
+ }
+
+ // Compute events based on the difference.
+ nsTArray<RefPtr<AccEvent> > events;
+ ComputeTextChangeEvents(str1, str2, entries, events);
+
+ delete[] entries;
+
+ // Fire events.
+ for (int32_t idx = events.Length() - 1; idx >= 0; idx--)
+ mDocument->FireDelayedEvent(events[idx]);
+
+ mDocument->MaybeNotifyOfValueChange(mHyperText);
+
+ // Update the text.
+ mTextLeaf->SetText(aNewText);
+}
+
+void TextUpdater::ComputeTextChangeEvents(
+ const nsAString& aStr1, const nsAString& aStr2, uint32_t* aEntries,
+ nsTArray<RefPtr<AccEvent> >& aEvents) {
+ int32_t colIdx = aStr1.Length(), rowIdx = aStr2.Length();
+
+ // Point at which strings last matched.
+ int32_t colEnd = colIdx;
+ int32_t rowEnd = rowIdx;
+
+ int32_t colLen = colEnd + 1;
+ uint32_t* row = aEntries + rowIdx * colLen;
+ uint32_t dist = row[colIdx]; // current Levenshtein distance
+ while (rowIdx && colIdx) { // stop when we can't move diagonally
+ if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
+ if (rowIdx < rowEnd) { // deal with any pending insertion
+ FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx), rowIdx,
+ aEvents);
+ }
+ if (colIdx < colEnd) { // deal with any pending deletion
+ FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx), rowIdx,
+ aEvents);
+ }
+
+ colEnd = --colIdx; // reset the match point
+ rowEnd = --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ --dist;
+ if (dist == row[colIdx - 1 - colLen]) { // substitution
+ --colIdx;
+ --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ if (dist == row[colIdx - colLen]) { // insertion
+ --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ if (dist == row[colIdx - 1]) { // deletion
+ --colIdx;
+ continue;
+ }
+ MOZ_ASSERT_UNREACHABLE("huh?");
+ return;
+ }
+
+ if (rowEnd) FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
+ if (colEnd) FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
+}
diff --git a/accessible/base/TextUpdater.h b/accessible/base/TextUpdater.h
new file mode 100644
index 0000000000..87905cf082
--- /dev/null
+++ b/accessible/base/TextUpdater.h
@@ -0,0 +1,95 @@
+/* -*- 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 mozilla_a11y_TextUpdater_h__
+#define mozilla_a11y_TextUpdater_h__
+
+#include "AccEvent.h"
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used to find a difference between old and new text and fire text change
+ * events.
+ */
+class TextUpdater {
+ public:
+ /**
+ * Start text of the text leaf update.
+ */
+ static void Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
+ const nsAString& aNewText);
+
+ private:
+ TextUpdater(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf)
+ : mDocument(aDocument),
+ mTextLeaf(aTextLeaf),
+ mHyperText(nullptr),
+ mTextOffset(-1) {}
+
+ ~TextUpdater() {
+ mDocument = nullptr;
+ mTextLeaf = nullptr;
+ mHyperText = nullptr;
+ }
+
+ /**
+ * Update text of the text leaf accessible, fire text change and value change
+ * (if applicable) events for its container hypertext accessible.
+ */
+ void DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
+ uint32_t aSkipStart);
+
+ private:
+ TextUpdater();
+ TextUpdater(const TextUpdater&);
+ TextUpdater& operator=(const TextUpdater&);
+
+ /**
+ * Fire text change events based on difference between strings.
+ */
+ void ComputeTextChangeEvents(const nsAString& aStr1, const nsAString& aStr2,
+ uint32_t* aEntries,
+ nsTArray<RefPtr<AccEvent> >& aEvents);
+
+ /**
+ * Helper to create text change events for inserted text.
+ */
+ inline void FireInsertEvent(const nsAString& aText, uint32_t aAddlOffset,
+ nsTArray<RefPtr<AccEvent> >& aEvents) {
+ RefPtr<AccEvent> event = new AccTextChangeEvent(
+ mHyperText, mTextOffset + aAddlOffset, aText, true);
+ aEvents.AppendElement(event);
+ }
+
+ /**
+ * Helper to create text change events for removed text.
+ */
+ inline void FireDeleteEvent(const nsAString& aText, uint32_t aAddlOffset,
+ nsTArray<RefPtr<AccEvent> >& aEvents) {
+ RefPtr<AccEvent> event = new AccTextChangeEvent(
+ mHyperText, mTextOffset + aAddlOffset, aText, false);
+ aEvents.AppendElement(event);
+ }
+
+ /**
+ * The constant used to skip string difference calculation in case of long
+ * strings.
+ */
+ const static uint32_t kMaxStrLen = 1 << 6;
+
+ private:
+ DocAccessible* mDocument;
+ TextLeafAccessible* mTextLeaf;
+ HyperTextAccessible* mHyperText;
+ int32_t mTextOffset;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TreeWalker.cpp b/accessible/base/TreeWalker.cpp
new file mode 100644
index 0000000000..a865088271
--- /dev/null
+++ b/accessible/base/TreeWalker.cpp
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TreeWalker.h"
+
+#include "Accessible.h"
+#include "AccIterator.h"
+#include "nsAccessibilityService.h"
+#include "DocAccessible.h"
+
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TreeWalker
+////////////////////////////////////////////////////////////////////////////////
+
+TreeWalker::TreeWalker(Accessible* aContext)
+ : mDoc(aContext->Document()),
+ mContext(aContext),
+ mAnchorNode(nullptr),
+ mARIAOwnsIdx(0),
+ mChildFilter(nsIContent::eSkipPlaceholderContent),
+ mFlags(0),
+ mPhase(eAtStart) {
+ mChildFilter |= nsIContent::eAllChildren;
+
+ mAnchorNode = mContext->IsDoc() ? mDoc->DocumentNode()->GetRootElement()
+ : mContext->GetContent();
+
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::TreeWalker(Accessible* aContext, nsIContent* aAnchorNode,
+ uint32_t aFlags)
+ : mDoc(aContext->Document()),
+ mContext(aContext),
+ mAnchorNode(aAnchorNode),
+ mARIAOwnsIdx(0),
+ mChildFilter(nsIContent::eSkipPlaceholderContent),
+ mFlags(aFlags),
+ mPhase(eAtStart) {
+ MOZ_ASSERT(mFlags & eWalkCache,
+ "This constructor cannot be used for tree creation");
+ MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
+
+ mChildFilter |= nsIContent::eAllChildren;
+
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode)
+ : mDoc(aDocument),
+ mContext(nullptr),
+ mAnchorNode(aAnchorNode),
+ mARIAOwnsIdx(0),
+ mChildFilter(nsIContent::eSkipPlaceholderContent |
+ nsIContent::eAllChildren),
+ mFlags(eWalkCache),
+ mPhase(eAtStart) {
+ MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::~TreeWalker() { MOZ_COUNT_DTOR(TreeWalker); }
+
+Accessible* TreeWalker::Scope(nsIContent* aAnchorNode) {
+ Reset();
+
+ mAnchorNode = aAnchorNode;
+
+ mFlags |= eScoped;
+
+ bool skipSubtree = false;
+ Accessible* acc = AccessibleFor(aAnchorNode, 0, &skipSubtree);
+ if (acc) {
+ mPhase = eAtEnd;
+ return acc;
+ }
+
+ return skipSubtree ? nullptr : Next();
+}
+
+bool TreeWalker::Seek(nsIContent* aChildNode) {
+ MOZ_ASSERT(aChildNode, "Child cannot be null");
+
+ Reset();
+
+ if (mAnchorNode == aChildNode) {
+ return true;
+ }
+
+ nsIContent* childNode = nullptr;
+ nsINode* parentNode = aChildNode;
+ do {
+ childNode = parentNode->AsContent();
+ parentNode = childNode->GetFlattenedTreeParent();
+
+ // Handle the special case of XBL binding child under a shadow root.
+ if (parentNode && parentNode->IsShadowRoot()) {
+ parentNode = childNode->GetFlattenedTreeParent();
+ if (parentNode == mAnchorNode) {
+ return true;
+ }
+ continue;
+ }
+
+ if (!parentNode || !parentNode->IsElement()) {
+ return false;
+ }
+
+ // If ARIA owned child.
+ Accessible* child = mDoc->GetAccessible(childNode);
+ if (child && child->IsRelocated()) {
+ MOZ_ASSERT(
+ !(mFlags & eScoped),
+ "Walker should not be scoped when seeking into relocated children");
+ if (child->Parent() != mContext) {
+ return false;
+ }
+
+ Accessible* ownedChild = nullptr;
+ while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
+ ownedChild != child)
+ ;
+
+ MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
+ mPhase = eAtARIAOwns;
+ return true;
+ }
+
+ // Look in DOM.
+ dom::AllChildrenIterator* iter =
+ PrependState(parentNode->AsElement(), true);
+ if (!iter->Seek(childNode)) {
+ return false;
+ }
+
+ if (parentNode == mAnchorNode) {
+ mPhase = eAtDOM;
+ return true;
+ }
+ } while (true);
+
+ return false;
+}
+
+Accessible* TreeWalker::Next() {
+ if (mStateStack.IsEmpty()) {
+ if (mPhase == eAtEnd) {
+ return nullptr;
+ }
+
+ if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
+ if (!(mFlags & eScoped)) {
+ mPhase = eAtARIAOwns;
+ Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
+ if (child) {
+ mARIAOwnsIdx++;
+ return child;
+ }
+ }
+ MOZ_ASSERT(!(mFlags & eScoped) || mPhase != eAtARIAOwns,
+ "Don't walk relocated children in scoped mode");
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+
+ if (!mAnchorNode) {
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+
+ mPhase = eAtDOM;
+ PushState(mAnchorNode, true);
+ }
+
+ dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+ while (top) {
+ while (nsIContent* childNode = top->GetNextChild()) {
+ bool skipSubtree = false;
+ Accessible* child = AccessibleFor(childNode, mFlags, &skipSubtree);
+ if (child) {
+ return child;
+ }
+
+ // Walk down the subtree if allowed.
+ if (!skipSubtree && childNode->IsElement()) {
+ top = PushState(childNode, true);
+ }
+ }
+ top = PopState();
+ }
+
+ // If we traversed the whole subtree of the anchor node. Move to next node
+ // relative anchor node within the context subtree if asked.
+ if (mFlags != eWalkContextTree) {
+ // eWalkCache flag presence indicates that the search is scoped to the
+ // anchor (no ARIA owns stuff).
+ if (mFlags & eWalkCache) {
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+ return Next();
+ }
+
+ nsINode* contextNode = mContext->GetNode();
+ while (mAnchorNode != contextNode) {
+ nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
+ if (!parentNode || !parentNode->IsElement()) return nullptr;
+
+ nsIContent* parent = parentNode->AsElement();
+ top = PushState(parent, true);
+ if (top->Seek(mAnchorNode)) {
+ mAnchorNode = parent;
+ return Next();
+ }
+
+ // XXX We really should never get here, it means we're trying to find an
+ // accessible for a dom node where iterating over its parent's children
+ // doesn't return it. However this sometimes happens when we're asked for
+ // the nearest accessible to place holder content which we ignore.
+ mAnchorNode = parent;
+ }
+
+ return Next();
+}
+
+Accessible* TreeWalker::Prev() {
+ if (mStateStack.IsEmpty()) {
+ if (mPhase == eAtStart || mPhase == eAtDOM) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ if (mPhase == eAtEnd) {
+ if (mFlags & eScoped) {
+ mPhase = eAtDOM;
+ } else {
+ mPhase = eAtARIAOwns;
+ mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
+ }
+ }
+
+ if (mPhase == eAtARIAOwns) {
+ MOZ_ASSERT(!(mFlags & eScoped),
+ "Should not walk relocated children in scoped mode");
+ if (mARIAOwnsIdx > 0) {
+ return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
+ }
+
+ if (!mAnchorNode) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ mPhase = eAtDOM;
+ PushState(mAnchorNode, false);
+ }
+ }
+
+ dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+ while (top) {
+ while (nsIContent* childNode = top->GetPreviousChild()) {
+ // No accessible creation on the way back.
+ bool skipSubtree = false;
+ Accessible* child = AccessibleFor(childNode, eWalkCache, &skipSubtree);
+ if (child) {
+ return child;
+ }
+
+ // Walk down into subtree to find accessibles.
+ if (!skipSubtree && childNode->IsElement()) {
+ top = PushState(childNode, false);
+ }
+ }
+ top = PopState();
+ }
+
+ // Move to a previous node relative the anchor node within the context
+ // subtree if asked.
+ if (mFlags != eWalkContextTree) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ nsINode* contextNode = mContext->GetNode();
+ while (mAnchorNode != contextNode) {
+ nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
+ if (!parentNode || !parentNode->IsElement()) {
+ return nullptr;
+ }
+
+ nsIContent* parent = parentNode->AsElement();
+ top = PushState(parent, true);
+ if (top->Seek(mAnchorNode)) {
+ mAnchorNode = parent;
+ return Prev();
+ }
+
+ mAnchorNode = parent;
+ }
+
+ mPhase = eAtStart;
+ return nullptr;
+}
+
+Accessible* TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags,
+ bool* aSkipSubtree) {
+ // Ignore the accessible and its subtree if it was repositioned by means
+ // of aria-owns.
+ Accessible* child = mDoc->GetAccessible(aNode);
+ if (child) {
+ if (child->IsRelocated()) {
+ *aSkipSubtree = true;
+ return nullptr;
+ }
+ return child;
+ }
+
+ // Create an accessible if allowed.
+ if (!(aFlags & eWalkCache) && mContext->IsAcceptableChild(aNode)) {
+ // We may have ARIA owned element in the dependent attributes map, but the
+ // element may be not allowed for this ARIA owns relation, if the relation
+ // crosses out XBL anonymous content boundaries. In this case we won't
+ // create an accessible object for it, when aria-owns is processed, which
+ // may make the element subtree inaccessible. To avoid that let's create
+ // an accessible object now, and later, if allowed, move it in the tree,
+ // when aria-owns relation is processed.
+ if (mDoc->RelocateARIAOwnedIfNeeded(aNode) && !aNode->IsXULElement()) {
+ *aSkipSubtree = true;
+ return nullptr;
+ }
+ return GetAccService()->CreateAccessible(aNode, mContext, aSkipSubtree);
+ }
+
+ return nullptr;
+}
+
+dom::AllChildrenIterator* TreeWalker::PopState() {
+ mStateStack.RemoveLastElement();
+ return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
+}
diff --git a/accessible/base/TreeWalker.h b/accessible/base/TreeWalker.h
new file mode 100644
index 0000000000..94d06d524a
--- /dev/null
+++ b/accessible/base/TreeWalker.h
@@ -0,0 +1,142 @@
+/* -*- 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 mozilla_a11y_TreeWalker_h_
+#define mozilla_a11y_TreeWalker_h_
+
+#include "mozilla/Attributes.h"
+#include <stdint.h>
+#include "mozilla/dom/ChildIterator.h"
+#include "nsCOMPtr.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class DocAccessible;
+
+/**
+ * This class is used to walk the DOM tree to create accessible tree.
+ */
+class TreeWalker final {
+ public:
+ enum {
+ // used to walk the existing tree of the given node
+ eWalkCache = 1,
+ // used to walk the context tree starting from given node
+ eWalkContextTree = 2 | eWalkCache,
+ eScoped = 4
+ };
+
+ /**
+ * Used to navigate and create if needed the accessible children.
+ */
+ explicit TreeWalker(Accessible* aContext);
+
+ /**
+ * Used to navigate the accessible children relative to the anchor.
+ *
+ * @param aContext [in] container accessible for the given node, used to
+ * define accessible context
+ * @param aAnchorNode [in] the node the search will be prepared relative to
+ * @param aFlags [in] flags (see enum above)
+ */
+ TreeWalker(Accessible* aContext, nsIContent* aAnchorNode,
+ uint32_t aFlags = eWalkCache);
+
+ /**
+ * Navigates the accessible children within the anchor node subtree.
+ */
+ TreeWalker(DocAccessible* aDocument, nsIContent* aAnchorNode);
+
+ ~TreeWalker();
+
+ /**
+ * Resets the walker state, and sets the given node as an anchor. Returns a
+ * first accessible element within the node including the node itself.
+ */
+ Accessible* Scope(nsIContent* aAnchorNode);
+
+ /**
+ * Resets the walker state.
+ */
+ void Reset() {
+ mPhase = eAtStart;
+ mStateStack.Clear();
+ mARIAOwnsIdx = 0;
+ }
+
+ /**
+ * Sets the walker state to the given child node if it's within the anchor.
+ */
+ bool Seek(nsIContent* aChildNode);
+
+ /**
+ * Return the next/prev accessible.
+ *
+ * @note Returned accessible is bound to the document, if the accessible is
+ * rejected during tree creation then the caller should be unbind it
+ * from the document.
+ */
+ Accessible* Next();
+ Accessible* Prev();
+
+ Accessible* Context() const { return mContext; }
+ DocAccessible* Document() const { return mDoc; }
+
+ private:
+ TreeWalker();
+ TreeWalker(const TreeWalker&);
+ TreeWalker& operator=(const TreeWalker&);
+
+ /**
+ * Return an accessible for the given node if any.
+ */
+ Accessible* AccessibleFor(nsIContent* aNode, uint32_t aFlags,
+ bool* aSkipSubtree);
+
+ /**
+ * Create new state for the given node and push it on top of stack / at bottom
+ * of stack.
+ *
+ * @note State stack is used to navigate up/down the DOM subtree during
+ * accessible children search.
+ */
+ dom::AllChildrenIterator* PushState(nsIContent* aContent,
+ bool aStartAtBeginning) {
+ return mStateStack.AppendElement(
+ dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
+ }
+ dom::AllChildrenIterator* PrependState(nsIContent* aContent,
+ bool aStartAtBeginning) {
+ return mStateStack.InsertElementAt(
+ 0, dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
+ }
+
+ /**
+ * Pop state from stack.
+ */
+ dom::AllChildrenIterator* PopState();
+
+ DocAccessible* mDoc;
+ Accessible* mContext;
+ nsIContent* mAnchorNode;
+
+ AutoTArray<dom::AllChildrenIterator, 20> mStateStack;
+ uint32_t mARIAOwnsIdx;
+
+ int32_t mChildFilter;
+ uint32_t mFlags;
+
+ enum Phase { eAtStart, eAtDOM, eAtARIAOwns, eAtEnd };
+ Phase mPhase;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_TreeWalker_h_
diff --git a/accessible/base/XULMap.h b/accessible/base/XULMap.h
new file mode 100644
index 0000000000..4ebe7a6245
--- /dev/null
+++ b/accessible/base/XULMap.h
@@ -0,0 +1,126 @@
+/* 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/. */
+
+XULMAP_TYPE(browser, OuterDocAccessible)
+XULMAP_TYPE(button, XULButtonAccessible)
+XULMAP_TYPE(checkbox, CheckboxAccessible)
+XULMAP_TYPE(dropMarker, XULDropmarkerAccessible)
+XULMAP_TYPE(editor, OuterDocAccessible)
+XULMAP_TYPE(findbar, XULToolbarAccessible)
+XULMAP_TYPE(groupbox, XULGroupboxAccessible)
+XULMAP_TYPE(iframe, OuterDocAccessible)
+XULMAP_TYPE(listheader, XULColumAccessible)
+XULMAP_TYPE(menu, XULMenuitemAccessibleWrap)
+XULMAP_TYPE(menubar, XULMenubarAccessible)
+XULMAP_TYPE(menucaption, XULMenuitemAccessibleWrap)
+XULMAP_TYPE(menuitem, XULMenuitemAccessibleWrap)
+XULMAP_TYPE(menulist, XULComboboxAccessible)
+XULMAP_TYPE(menuseparator, XULMenuSeparatorAccessible)
+XULMAP_TYPE(notification, XULAlertAccessible)
+XULMAP_TYPE(radio, XULRadioButtonAccessible)
+XULMAP_TYPE(radiogroup, XULRadioGroupAccessible)
+XULMAP_TYPE(richlistbox, XULListboxAccessibleWrap)
+XULMAP_TYPE(richlistitem, XULListitemAccessible)
+XULMAP_TYPE(statusbar, XULStatusBarAccessible)
+XULMAP_TYPE(tab, XULTabAccessible)
+XULMAP_TYPE(tabpanels, XULTabpanelsAccessible)
+XULMAP_TYPE(tabs, XULTabsAccessible)
+XULMAP_TYPE(toolbarseparator, XULToolbarSeparatorAccessible)
+XULMAP_TYPE(toolbarspacer, XULToolbarSeparatorAccessible)
+XULMAP_TYPE(toolbarspring, XULToolbarSeparatorAccessible)
+XULMAP_TYPE(treecol, XULColumnItemAccessible)
+XULMAP_TYPE(treecolpicker, XULButtonAccessible)
+XULMAP_TYPE(treecols, XULTreeColumAccessible)
+XULMAP_TYPE(toolbar, XULToolbarAccessible)
+XULMAP_TYPE(toolbarbutton, XULToolbarButtonAccessible)
+
+XULMAP(description, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aElement->ClassList()->Contains(u"tooltip-label"_ns)) {
+ return nullptr;
+ }
+
+ return new XULLabelAccessible(aElement, aContext->Document());
+})
+
+XULMAP(tooltip, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+ if (!popupFrame) {
+ return nullptr;
+ }
+
+ nsPopupState popupState = popupFrame->PopupState();
+ if (popupState == ePopupHiding || popupState == ePopupInvisible ||
+ popupState == ePopupClosed) {
+ return nullptr;
+ }
+
+ return new XULTooltipAccessible(aElement, aContext->Document());
+})
+
+XULMAP(label, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ if (aElement->ClassList()->Contains(u"text-link"_ns)) {
+ return new XULLinkAccessible(aElement, aContext->Document());
+ }
+ return new XULLabelAccessible(aElement, aContext->Document());
+})
+
+XULMAP(image, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ // Don't include nameless images in accessible tree.
+ if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext)) {
+ return nullptr;
+ }
+
+ return new ImageAccessibleWrap(aElement, aContext->Document());
+})
+
+XULMAP(menupopup, [](Element* aElement, Accessible* aContext) {
+ return CreateMenupopupAccessible(aElement, aContext);
+})
+
+XULMAP(panel, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ static const Element::AttrValuesArray sIgnoreTypeVals[] = {
+ nsGkAtoms::autocomplete_richlistbox, nsGkAtoms::autocomplete, nullptr};
+
+ if (aElement->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ sIgnoreTypeVals, eIgnoreCase) >= 0) {
+ return nullptr;
+ }
+
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return new XULAlertAccessible(aElement, aContext->Document());
+ }
+
+ return new EnumRoleAccessible<roles::PANE>(aElement, aContext->Document());
+})
+
+XULMAP(popup, [](Element* aElement, Accessible* aContext) {
+ return CreateMenupopupAccessible(aElement, aContext);
+})
+
+XULMAP(tree, [](Element* aElement, Accessible* aContext) -> Accessible* {
+ nsIContent* child =
+ nsTreeUtils::GetDescendantChild(aElement, nsGkAtoms::treechildren);
+ if (!child) return nullptr;
+
+ nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame());
+ if (!treeFrame) return nullptr;
+
+ RefPtr<nsTreeColumns> treeCols = treeFrame->Columns();
+ uint32_t count = treeCols->Count();
+
+ // Outline of list accessible.
+ if (count == 1) {
+ return new XULTreeAccessible(aElement, aContext->Document(), treeFrame);
+ }
+
+ // Table or tree table accessible.
+ return new XULTreeGridAccessibleWrap(aElement, aContext->Document(),
+ treeFrame);
+})
diff --git a/accessible/base/moz.build b/accessible/base/moz.build
new file mode 100644
index 0000000000..6eb2d96b04
--- /dev/null
+++ b/accessible/base/moz.build
@@ -0,0 +1,117 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += ["AccEvent.h", "nsAccessibilityService.h"]
+
+EXPORTS.mozilla.a11y += [
+ "AccTypes.h",
+ "DocManager.h",
+ "FocusManager.h",
+ "IDSet.h",
+ "Platform.h",
+ "RelationType.h",
+ "Role.h",
+ "SelectionManager.h",
+ "States.h",
+]
+
+if CONFIG["MOZ_DEBUG"]:
+ EXPORTS.mozilla.a11y += [
+ "Logging.h",
+ ]
+
+UNIFIED_SOURCES += [
+ "AccessibleOrProxy.cpp",
+ "AccEvent.cpp",
+ "AccGroupInfo.cpp",
+ "AccIterator.cpp",
+ "ARIAMap.cpp",
+ "ARIAStateMap.cpp",
+ "Asserts.cpp",
+ "DocManager.cpp",
+ "EmbeddedObjCollector.cpp",
+ "EventQueue.cpp",
+ "EventTree.cpp",
+ "Filters.cpp",
+ "FocusManager.cpp",
+ "NotificationController.cpp",
+ "nsAccessibilityService.cpp",
+ "nsAccessiblePivot.cpp",
+ "nsAccUtils.cpp",
+ "nsCoreUtils.cpp",
+ "nsEventShell.cpp",
+ "nsTextEquivUtils.cpp",
+ "Pivot.cpp",
+ "SelectionManager.cpp",
+ "StyleInfo.cpp",
+ "TextAttrs.cpp",
+ "TextRange.cpp",
+ "TextUpdater.cpp",
+ "TreeWalker.cpp",
+]
+
+if CONFIG["A11Y_LOG"]:
+ UNIFIED_SOURCES += [
+ "Logging.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/accessible/generic",
+ "/accessible/html",
+ "/accessible/ipc",
+ "/dom/base",
+ "/dom/xul",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ LOCAL_INCLUDES += [
+ "/accessible/ipc/win",
+ ]
+else:
+ LOCAL_INCLUDES += [
+ "/accessible/ipc/other",
+ ]
+
+LOCAL_INCLUDES += [
+ "/accessible/xpcom",
+ "/accessible/xul",
+ "/dom/base",
+ "/ipc/chromium/src",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/layout/xul/tree/",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ LOCAL_INCLUDES += [
+ "/accessible/atk",
+ ]
+ CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ LOCAL_INCLUDES += [
+ "/accessible/windows/ia2",
+ "/accessible/windows/msaa",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/accessible/mac",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ LOCAL_INCLUDES += [
+ "/accessible/android",
+ ]
+else:
+ LOCAL_INCLUDES += [
+ "/accessible/other",
+ ]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/accessible/base/nsAccCache.h b/accessible/base/nsAccCache.h
new file mode 100644
index 0000000000..a6d5874867
--- /dev/null
+++ b/accessible/base/nsAccCache.h
@@ -0,0 +1,24 @@
+/* -*- 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 _nsAccCache_H_
+#define _nsAccCache_H_
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible cache utils
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void UnbindCacheEntriesFromDocument(
+ nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache) {
+ for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
+ T* accessible = iter.Data();
+ MOZ_ASSERT(accessible && !accessible->IsDefunct());
+ accessible->Document()->UnbindFromDocument(accessible);
+ iter.Remove();
+ }
+}
+
+#endif
diff --git a/accessible/base/nsAccUtils.cpp b/accessible/base/nsAccUtils.cpp
new file mode 100644
index 0000000000..49d0af6bd3
--- /dev/null
+++ b/accessible/base/nsAccUtils.cpp
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAccUtils.h"
+
+#include "Accessible-inl.h"
+#include "ARIAMap.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+#include "HyperTextAccessible.h"
+#include "nsIAccessibleTypes.h"
+#include "Role.h"
+#include "States.h"
+#include "TextLeafAccessible.h"
+
+#include "nsIDOMXULContainerElement.h"
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/a11y/PDocAccessibleChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsAccessibilityService.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void nsAccUtils::GetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, nsAString& aAttrValue) {
+ aAttrValue.Truncate();
+
+ aAttributes->GetStringProperty(nsAtomCString(aAttrName), aAttrValue);
+}
+
+void nsAccUtils::SetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, const nsAString& aAttrValue) {
+ nsAutoString oldValue;
+ aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue,
+ oldValue);
+}
+
+void nsAccUtils::SetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, nsAtom* aAttrValue) {
+ nsAutoString oldValue;
+ aAttributes->SetStringProperty(nsAtomCString(aAttrName),
+ nsAtomString(aAttrValue), oldValue);
+}
+
+void nsAccUtils::SetAccGroupAttrs(nsIPersistentProperties* aAttributes,
+ int32_t aLevel, int32_t aSetSize,
+ int32_t aPosInSet) {
+ nsAutoString value;
+
+ if (aLevel) {
+ value.AppendInt(aLevel);
+ SetAccAttr(aAttributes, nsGkAtoms::level, value);
+ }
+
+ if (aSetSize && aPosInSet) {
+ value.Truncate();
+ value.AppendInt(aPosInSet);
+ SetAccAttr(aAttributes, nsGkAtoms::posinset, value);
+
+ value.Truncate();
+ value.AppendInt(aSetSize);
+ SetAccAttr(aAttributes, nsGkAtoms::setsize, value);
+ }
+}
+
+int32_t nsAccUtils::GetDefaultLevel(const Accessible* aAccessible) {
+ roles::Role role = aAccessible->Role();
+
+ if (role == roles::OUTLINEITEM) return 1;
+
+ if (role == roles::ROW) {
+ Accessible* parent = aAccessible->Parent();
+ // It is a row inside flatten treegrid. Group level is always 1 until it
+ // is overriden by aria-level attribute.
+ if (parent && parent->Role() == roles::TREE_TABLE) return 1;
+ }
+
+ return 0;
+}
+
+int32_t nsAccUtils::GetARIAOrDefaultLevel(const Accessible* aAccessible) {
+ int32_t level = 0;
+ nsCoreUtils::GetUIntAttr(aAccessible->GetContent(), nsGkAtoms::aria_level,
+ &level);
+
+ if (level != 0) return level;
+
+ return GetDefaultLevel(aAccessible);
+}
+
+int32_t nsAccUtils::GetLevelForXULContainerItem(nsIContent* aContent) {
+ nsCOMPtr<nsIDOMXULContainerItemElement> item =
+ aContent->AsElement()->AsXULContainerItem();
+ if (!item) return 0;
+
+ nsCOMPtr<dom::Element> containerElement;
+ item->GetParentContainer(getter_AddRefs(containerElement));
+ nsCOMPtr<nsIDOMXULContainerElement> container =
+ containerElement ? containerElement->AsXULContainer() : nullptr;
+ if (!container) return 0;
+
+ // Get level of the item.
+ int32_t level = -1;
+ while (container) {
+ level++;
+
+ container->GetParentContainer(getter_AddRefs(containerElement));
+ container = containerElement ? containerElement->AsXULContainer() : nullptr;
+ }
+
+ return level;
+}
+
+void nsAccUtils::SetLiveContainerAttributes(
+ nsIPersistentProperties* aAttributes, nsIContent* aStartContent) {
+ nsAutoString live, relevant, busy;
+ dom::Document* doc = aStartContent->GetComposedDoc();
+ if (!doc) {
+ return;
+ }
+ dom::Element* topEl = doc->GetRootElement();
+ nsIContent* ancestor = aStartContent;
+ while (ancestor) {
+ // container-relevant attribute
+ if (relevant.IsEmpty() &&
+ HasDefinedARIAToken(ancestor, nsGkAtoms::aria_relevant) &&
+ ancestor->AsElement()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_relevant, relevant))
+ SetAccAttr(aAttributes, nsGkAtoms::containerRelevant, relevant);
+
+ // container-live, and container-live-role attributes
+ if (live.IsEmpty()) {
+ const nsRoleMapEntry* role = nullptr;
+ if (ancestor->IsElement()) {
+ role = aria::GetRoleMap(ancestor->AsElement());
+ }
+ if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+ ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live,
+ live);
+ } else if (role) {
+ GetLiveAttrValue(role->liveAttRule, live);
+ } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
+ ancestor, nsGkAtoms::live)) {
+ value->ToString(live);
+ }
+
+ if (!live.IsEmpty()) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLive, live);
+ if (role) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLiveRole,
+ role->ARIARoleString());
+ }
+ }
+ }
+
+ // container-atomic attribute
+ if (ancestor->IsElement() && ancestor->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::aria_atomic,
+ nsGkAtoms::_true, eCaseMatters)) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerAtomic, u"true"_ns);
+ }
+
+ // container-busy attribute
+ if (busy.IsEmpty() && HasDefinedARIAToken(ancestor, nsGkAtoms::aria_busy) &&
+ ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_busy,
+ busy))
+ SetAccAttr(aAttributes, nsGkAtoms::containerBusy, busy);
+
+ if (ancestor == topEl) {
+ break;
+ }
+
+ ancestor = ancestor->GetParent();
+ if (!ancestor) {
+ ancestor = topEl; // Use <body>/<frameset>
+ }
+ }
+}
+
+bool nsAccUtils::HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom) {
+ NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
+
+ if (!aContent->IsElement()) return false;
+
+ dom::Element* element = aContent->AsElement();
+ if (!element->HasAttr(kNameSpaceID_None, aAtom) ||
+ element->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_empty,
+ eCaseMatters) ||
+ element->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_undefined,
+ eCaseMatters)) {
+ return false;
+ }
+ return true;
+}
+
+nsStaticAtom* nsAccUtils::GetARIAToken(dom::Element* aElement, nsAtom* aAttr) {
+ if (!HasDefinedARIAToken(aElement, aAttr)) return nsGkAtoms::_empty;
+
+ static dom::Element::AttrValuesArray tokens[] = {
+ nsGkAtoms::_false, nsGkAtoms::_true, nsGkAtoms::mixed, nullptr};
+
+ int32_t idx =
+ aElement->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
+ if (idx >= 0) return tokens[idx];
+
+ return nullptr;
+}
+
+nsStaticAtom* nsAccUtils::NormalizeARIAToken(dom::Element* aElement,
+ nsAtom* aAttr) {
+ if (!HasDefinedARIAToken(aElement, aAttr)) {
+ return nsGkAtoms::_empty;
+ }
+
+ if (aAttr == nsGkAtoms::aria_current) {
+ static dom::Element::AttrValuesArray tokens[] = {
+ nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location_,
+ nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true,
+ nullptr};
+ int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens,
+ eCaseMatters);
+ // If the token is present, return it, otherwise TRUE as per spec.
+ return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true;
+ }
+
+ return nullptr;
+}
+
+Accessible* nsAccUtils::GetSelectableContainer(Accessible* aAccessible,
+ uint64_t aState) {
+ if (!aAccessible) return nullptr;
+
+ if (!(aState & states::SELECTABLE)) return nullptr;
+
+ Accessible* parent = aAccessible;
+ while ((parent = parent->Parent()) && !parent->IsSelect()) {
+ if (parent->Role() == roles::PANE) return nullptr;
+ }
+ return parent;
+}
+
+bool nsAccUtils::IsDOMAttrTrue(const Accessible* aAccessible, nsAtom* aAttr) {
+ dom::Element* el = aAccessible->Elm();
+ return el && el->AttrValueIs(kNameSpaceID_None, aAttr, nsGkAtoms::_true,
+ eCaseMatters);
+}
+
+Accessible* nsAccUtils::TableFor(Accessible* aRow) {
+ if (aRow) {
+ Accessible* table = aRow->Parent();
+ if (table) {
+ roles::Role tableRole = table->Role();
+ const nsRoleMapEntry* roleMapEntry = table->ARIARoleMap();
+ if (tableRole == roles::GROUPING || // if there's a rowgroup.
+ (table->IsGenericHyperText() && !roleMapEntry &&
+ !table->IsTable())) { // or there is a wrapping text container
+ table = table->Parent();
+ if (table) tableRole = table->Role();
+ }
+
+ return (tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ||
+ tableRole == roles::MATHML_TABLE)
+ ? table
+ : nullptr;
+ }
+ }
+
+ return nullptr;
+}
+
+HyperTextAccessible* nsAccUtils::GetTextContainer(nsINode* aNode) {
+ // Get text accessible containing the result node.
+ DocAccessible* doc = GetAccService()->GetDocAccessible(aNode->OwnerDoc());
+ Accessible* accessible = doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
+ if (!accessible) return nullptr;
+
+ do {
+ HyperTextAccessible* textAcc = accessible->AsHyperText();
+ if (textAcc) return textAcc;
+
+ accessible = accessible->Parent();
+ } while (accessible);
+
+ return nullptr;
+}
+
+nsIntPoint nsAccUtils::ConvertToScreenCoords(int32_t aX, int32_t aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible) {
+ nsIntPoint coords(aX, aY);
+
+ switch (aCoordinateType) {
+ case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
+ break;
+
+ case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
+ coords += nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
+ break;
+ }
+
+ case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
+ coords += GetScreenCoordsForParent(aAccessible);
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid coord type!");
+ }
+
+ return coords;
+}
+
+void nsAccUtils::ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible) {
+ switch (aCoordinateType) {
+ case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
+ break;
+
+ case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
+ nsIntPoint coords =
+ nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
+ *aX -= coords.x;
+ *aY -= coords.y;
+ break;
+ }
+
+ case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
+ nsIntPoint coords = GetScreenCoordsForParent(aAccessible);
+ *aX -= coords.x;
+ *aY -= coords.y;
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid coord type!");
+ }
+}
+
+nsIntPoint nsAccUtils::GetScreenCoordsForParent(Accessible* aAccessible) {
+ Accessible* parent = aAccessible->Parent();
+ if (!parent) return nsIntPoint(0, 0);
+
+ nsIFrame* parentFrame = parent->GetFrame();
+ if (!parentFrame) return nsIntPoint(0, 0);
+
+ nsRect rect = parentFrame->GetScreenRectInAppUnits();
+ return nsPoint(rect.X(), rect.Y())
+ .ToNearestPixels(parentFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+bool nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) {
+ switch (aRule) {
+ case eOffLiveAttr:
+ aValue = u"off"_ns;
+ return true;
+ case ePoliteLiveAttr:
+ aValue = u"polite"_ns;
+ return true;
+ case eAssertiveLiveAttr:
+ aValue = u"assertive"_ns;
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef DEBUG
+
+bool nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible) {
+ // Don't test for accessible docs, it makes us create accessibles too
+ // early and fire mutation events before we need to
+ if (aAccessible->IsDoc()) return true;
+
+ bool foundText = false;
+ uint32_t childCount = aAccessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = aAccessible->GetChildAt(childIdx);
+ if (child->IsText()) {
+ foundText = true;
+ break;
+ }
+ }
+
+ return !foundText || aAccessible->IsHyperText();
+}
+#endif
+
+uint32_t nsAccUtils::TextLength(Accessible* aAccessible) {
+ if (!aAccessible->IsText()) {
+ return 1;
+ }
+
+ TextLeafAccessible* textLeaf = aAccessible->AsTextLeaf();
+ if (textLeaf) return textLeaf->Text().Length();
+
+ // For list bullets (or anything other accessible which would compute its own
+ // text. They don't have their own frame.
+ // XXX In the future, list bullets may have frame and anon content, so
+ // we should be able to remove this at that point
+ nsAutoString text;
+ aAccessible->AppendTextTo(text); // Get all the text
+ return text.Length();
+}
+
+bool nsAccUtils::MustPrune(AccessibleOrProxy aAccessible) {
+ MOZ_ASSERT(!aAccessible.IsNull());
+ roles::Role role = aAccessible.Role();
+
+ if (role == roles::SLIDER) {
+ // Always prune the tree for sliders, as it doesn't make sense for a
+ // slider to have descendants and this confuses NVDA.
+ return true;
+ }
+
+ if (role != roles::MENUITEM && role != roles::COMBOBOX_OPTION &&
+ role != roles::OPTION && role != roles::ENTRY &&
+ role != roles::FLAT_EQUATION && role != roles::PASSWORD_TEXT &&
+ role != roles::PUSHBUTTON && role != roles::TOGGLE_BUTTON &&
+ role != roles::GRAPHIC && role != roles::PROGRESSBAR &&
+ role != roles::SEPARATOR) {
+ // If it doesn't match any of these roles, don't prune its children.
+ return false;
+ }
+
+ if (aAccessible.ChildCount() != 1) {
+ // If the accessible has more than one child, don't prune it.
+ return false;
+ }
+
+ roles::Role childRole = aAccessible.FirstChild().Role();
+ // If the accessible's child is a text leaf, prune the accessible.
+ return childRole == roles::TEXT_LEAF || childRole == roles::STATICTEXT;
+}
+
+bool nsAccUtils::PersistentPropertiesToArray(nsIPersistentProperties* aProps,
+ nsTArray<Attribute>* aAttributes) {
+ if (!aProps) {
+ return true;
+ }
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ nsresult rv = aProps->Enumerate(getter_AddRefs(propEnum));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ rv = propEnum->GetNext(getter_AddRefs(sup));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
+ NS_ENSURE_TRUE(propElem, false);
+
+ nsAutoCString name;
+ rv = propElem->GetKey(name);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString value;
+ rv = propElem->GetValue(value);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ aAttributes->AppendElement(Attribute(name, value));
+ }
+
+ return true;
+}
+
+bool nsAccUtils::IsARIALive(const Accessible* aAccessible) {
+ // Get computed aria-live property based on the closest container with the
+ // attribute. Inner nodes override outer nodes within the same
+ // document.
+ // This should be the same as the container-live attribute, but we don't need
+ // the other container-* attributes, so we can't use the same function.
+ nsIContent* ancestor = aAccessible->GetContent();
+ if (!ancestor) {
+ return false;
+ }
+ dom::Document* doc = ancestor->GetComposedDoc();
+ if (!doc) {
+ return false;
+ }
+ dom::Element* topEl = doc->GetRootElement();
+ while (ancestor) {
+ const nsRoleMapEntry* role = nullptr;
+ if (ancestor->IsElement()) {
+ role = aria::GetRoleMap(ancestor->AsElement());
+ }
+ nsAutoString live;
+ if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+ ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live,
+ live);
+ } else if (role) {
+ GetLiveAttrValue(role->liveAttRule, live);
+ } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
+ ancestor, nsGkAtoms::live)) {
+ value->ToString(live);
+ }
+ if (!live.IsEmpty() && !live.EqualsLiteral("off")) {
+ return true;
+ }
+
+ if (ancestor == topEl) {
+ break;
+ }
+
+ ancestor = ancestor->GetParent();
+ if (!ancestor) {
+ ancestor = topEl; // Use <body>/<frameset>
+ }
+ }
+
+ return false;
+}
diff --git a/accessible/base/nsAccUtils.h b/accessible/base/nsAccUtils.h
new file mode 100644
index 0000000000..b44caed20a
--- /dev/null
+++ b/accessible/base/nsAccUtils.h
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAccUtils_h_
+#define nsAccUtils_h_
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/DocManager.h"
+
+#include "AccessibleOrProxy.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+
+#include "nsIDocShell.h"
+#include "nsPoint.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace dom {
+class Element;
+}
+
+namespace a11y {
+
+class HyperTextAccessible;
+class DocAccessible;
+class Attribute;
+
+class nsAccUtils {
+ public:
+ /**
+ * Returns value of attribute from the given attributes container.
+ *
+ * @param aAttributes - attributes container
+ * @param aAttrName - the name of requested attribute
+ * @param aAttrValue - value of attribute
+ */
+ static void GetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, nsAString& aAttrValue);
+
+ /**
+ * Set value of attribute for the given attributes container.
+ *
+ * @param aAttributes - attributes container
+ * @param aAttrName - the name of requested attribute
+ * @param aAttrValue - new value of attribute
+ */
+ static void SetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, const nsAString& aAttrValue);
+
+ static void SetAccAttr(nsIPersistentProperties* aAttributes,
+ nsAtom* aAttrName, nsAtom* aAttrValue);
+
+ /**
+ * Set group attributes ('level', 'setsize', 'posinset').
+ */
+ static void SetAccGroupAttrs(nsIPersistentProperties* aAttributes,
+ int32_t aLevel, int32_t aSetSize,
+ int32_t aPosInSet);
+
+ /**
+ * Get default value of the level for the given accessible.
+ */
+ static int32_t GetDefaultLevel(const Accessible* aAcc);
+
+ /**
+ * Return ARIA level value or the default one if ARIA is missed for the
+ * given accessible.
+ */
+ static int32_t GetARIAOrDefaultLevel(const Accessible* aAccessible);
+
+ /**
+ * Compute group level for nsIDOMXULContainerItemElement node.
+ */
+ static int32_t GetLevelForXULContainerItem(nsIContent* aContent);
+
+ /**
+ * Set container-foo live region attributes for the given node.
+ *
+ * @param aAttributes where to store the attributes
+ * @param aStartContent node to start from
+ */
+ static void SetLiveContainerAttributes(nsIPersistentProperties* aAttributes,
+ nsIContent* aStartContent);
+
+ /**
+ * Any ARIA property of type boolean or NMTOKEN is undefined if the ARIA
+ * property is not present, or is "" or "undefined". Do not call
+ * this method for properties of type string, decimal, IDREF or IDREFS.
+ *
+ * Return true if the ARIA property is defined, otherwise false
+ */
+ static bool HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom);
+
+ /**
+ * Return atomic value of ARIA attribute of boolean or NMTOKEN type.
+ */
+ static nsStaticAtom* GetARIAToken(mozilla::dom::Element* aElement,
+ nsAtom* aAttr);
+
+ /**
+ * If the given ARIA attribute has a specific known token value, return it.
+ * If the specification demands for a fallback value for unknown attribute
+ * values, return that. For all others, return a nullptr.
+ */
+ static nsStaticAtom* NormalizeARIAToken(mozilla::dom::Element* aElement,
+ nsAtom* aAttr);
+
+ /**
+ * Return document accessible for the given DOM node.
+ */
+ static DocAccessible* GetDocAccessibleFor(nsINode* aNode) {
+ return GetAccService()->GetDocAccessible(
+ nsCoreUtils::GetPresShellFor(aNode));
+ }
+
+ /**
+ * Return document accessible for the given docshell.
+ */
+ static DocAccessible* GetDocAccessibleFor(nsIDocShellTreeItem* aContainer) {
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
+ return GetAccService()->GetDocAccessible(docShell->GetPresShell());
+ }
+
+ /**
+ * Return single or multi selectable container for the given item.
+ *
+ * @param aAccessible [in] the item accessible
+ * @param aState [in] the state of the item accessible
+ */
+ static Accessible* GetSelectableContainer(Accessible* aAccessible,
+ uint64_t aState);
+
+ /**
+ * Return a text container accessible for the given node.
+ */
+ static HyperTextAccessible* GetTextContainer(nsINode* aNode);
+
+ static Accessible* TableFor(Accessible* aRow);
+
+ /**
+ * Return true if the DOM node of a given accessible has a given attribute
+ * with a value of "true".
+ */
+ static bool IsDOMAttrTrue(const Accessible* aAccessible, nsAtom* aAttr);
+
+ /**
+ * Return true if the DOM node of given accessible has aria-selected="true"
+ * attribute.
+ */
+ static inline bool IsARIASelected(const Accessible* aAccessible) {
+ return IsDOMAttrTrue(aAccessible, nsGkAtoms::aria_selected);
+ }
+
+ /**
+ * Return true if the DOM node of given accessible has
+ * aria-multiselectable="true" attribute.
+ */
+ static inline bool IsARIAMultiSelectable(const Accessible* aAccessible) {
+ return IsDOMAttrTrue(aAccessible, nsGkAtoms::aria_multiselectable);
+ }
+
+ /**
+ * Converts the given coordinates to coordinates relative screen.
+ *
+ * @param aX [in] the given x coord
+ * @param aY [in] the given y coord
+ * @param aCoordinateType [in] specifies coordinates origin (refer to
+ * nsIAccessibleCoordinateType)
+ * @param aAccessible [in] the accessible if coordinates are given
+ * relative it.
+ * @return converted coordinates
+ */
+ static nsIntPoint ConvertToScreenCoords(int32_t aX, int32_t aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible);
+
+ /**
+ * Converts the given coordinates relative screen to another coordinate
+ * system.
+ *
+ * @param aX [in, out] the given x coord
+ * @param aY [in, out] the given y coord
+ * @param aCoordinateType [in] specifies coordinates origin (refer to
+ * nsIAccessibleCoordinateType)
+ * @param aAccessible [in] the accessible if coordinates are given
+ * relative it
+ */
+ static void ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible);
+
+ /**
+ * Returns coordinates relative screen for the parent of the given accessible.
+ *
+ * @param [in] aAccessible the accessible
+ */
+ static nsIntPoint GetScreenCoordsForParent(Accessible* aAccessible);
+
+ /**
+ * Get the 'live' or 'container-live' object attribute value from the given
+ * ELiveAttrRule constant.
+ *
+ * @param aRule [in] rule constant (see ELiveAttrRule in nsAccMap.h)
+ * @param aValue [out] object attribute value
+ *
+ * @return true if object attribute should be exposed
+ */
+ static bool GetLiveAttrValue(uint32_t aRule, nsAString& aValue);
+
+#ifdef DEBUG
+ /**
+ * Detect whether the given accessible object implements nsIAccessibleText,
+ * when it is text or has text child node.
+ */
+ static bool IsTextInterfaceSupportCorrect(Accessible* aAccessible);
+#endif
+
+ /**
+ * Return text length of the given accessible, return 0 on failure.
+ */
+ static uint32_t TextLength(Accessible* aAccessible);
+
+ /**
+ * Transform nsIAccessibleStates constants to internal state constant.
+ */
+ static inline uint64_t To64State(uint32_t aState1, uint32_t aState2) {
+ return static_cast<uint64_t>(aState1) +
+ (static_cast<uint64_t>(aState2) << 31);
+ }
+
+ /**
+ * Transform internal state constant to nsIAccessibleStates constants.
+ */
+ static inline void To32States(uint64_t aState64, uint32_t* aState1,
+ uint32_t* aState2) {
+ *aState1 = aState64 & 0x7fffffff;
+ if (aState2) *aState2 = static_cast<uint32_t>(aState64 >> 31);
+ }
+
+ static uint32_t To32States(uint64_t aState, bool* aIsExtra) {
+ uint32_t extraState = aState >> 31;
+ *aIsExtra = !!extraState;
+ return aState | extraState;
+ }
+
+ /**
+ * Return true if the given accessible can't have children. Used when exposing
+ * to platform accessibility APIs, should the children be pruned off?
+ */
+ static bool MustPrune(AccessibleOrProxy aAccessible);
+
+ static bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
+ nsTArray<Attribute>* aAttributes);
+
+ /**
+ * Return true if the given accessible is within an ARIA live region; i.e.
+ * the container-live attribute would be something other than "off" or empty.
+ */
+ static bool IsARIALive(const Accessible* aAccessible);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
new file mode 100644
index 0000000000..7b14106d64
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -0,0 +1,1768 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAccessibilityService.h"
+
+// NOTE: alphabetically ordered
+#include "ApplicationAccessibleWrap.h"
+#include "ARIAGridAccessibleWrap.h"
+#include "ARIAMap.h"
+#include "DocAccessible-inl.h"
+#include "FocusManager.h"
+#include "HTMLCanvasAccessible.h"
+#include "HTMLElementAccessibles.h"
+#include "HTMLImageMapAccessible.h"
+#include "HTMLLinkAccessible.h"
+#include "HTMLListAccessible.h"
+#include "HTMLSelectAccessible.h"
+#include "HTMLTableAccessibleWrap.h"
+#include "HyperTextAccessibleWrap.h"
+#include "RootAccessible.h"
+#include "StyleInfo.h"
+#include "nsAccUtils.h"
+#include "nsArrayUtils.h"
+#include "nsAttrName.h"
+#include "nsDOMTokenList.h"
+#include "nsCRT.h"
+#include "nsEventShell.h"
+#include "nsIFrameInlines.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFormatter.h"
+#include "OuterDocAccessible.h"
+#include "Role.h"
+#ifdef MOZ_ACCESSIBILITY_ATK
+# include "RootAccessibleWrap.h"
+#endif
+#include "States.h"
+#include "Statistics.h"
+#include "TextLeafAccessibleWrap.h"
+#include "TreeWalker.h"
+#include "xpcAccessibleApplication.h"
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+# include "AtkSocketAccessible.h"
+#endif
+
+#ifdef XP_WIN
+# include "mozilla/a11y/Compatibility.h"
+# include "mozilla/dom/ContentChild.h"
+# include "HTMLWin32ObjectAccessible.h"
+# include "mozilla/StaticPtr.h"
+#endif
+
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+
+#include "nsExceptionHandler.h"
+#include "nsImageFrame.h"
+#include "nsINamed.h"
+#include "nsIObserverService.h"
+#include "nsMenuPopupFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPluginFrame.h"
+#include "nsTreeBodyFrame.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/HTMLTableElement.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Services.h"
+#include "mozilla/SVGGeometryFrame.h"
+#include "nsDeckFrame.h"
+
+#ifdef MOZ_XUL
+# include "XULAlertAccessible.h"
+# include "XULComboboxAccessible.h"
+# include "XULElementAccessibles.h"
+# include "XULFormControlAccessible.h"
+# include "XULListboxAccessibleWrap.h"
+# include "XULMenuAccessibleWrap.h"
+# include "XULTabAccessible.h"
+# include "XULTreeGridAccessibleWrap.h"
+#endif
+
+#if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
+# include "nsNPAPIPluginInstance.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+/**
+ * Accessibility service force enable/disable preference.
+ * Supported values:
+ * Accessibility is force enabled (accessibility should always be enabled): -1
+ * Accessibility is enabled (will be started upon a request, default value): 0
+ * Accessibility is force disabled (never enable accessibility): 1
+ */
+#define PREF_ACCESSIBILITY_FORCE_DISABLED "accessibility.force_disabled"
+
+////////////////////////////////////////////////////////////////////////////////
+// Statics
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Return true if the element must be accessible.
+ */
+static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
+ if (aContent->GetPrimaryFrame()->IsFocusable()) return true;
+
+ if (aContent->IsElement()) {
+ uint32_t attrCount = aContent->AsElement()->GetAttrCount();
+ for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
+ const nsAttrName* attr = aContent->AsElement()->GetAttrNameAt(attrIdx);
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsAtom* attrAtom = attr->Atom();
+ if (attrAtom == nsGkAtoms::title && aContent->IsHTMLElement()) {
+ // If the author provided a title on an element that would not
+ // be accessible normally, assume an intent and make it accessible.
+ return true;
+ }
+
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, u"aria-"_ns)) continue; // not ARIA
+
+ // A global state or a property and in case of token defined.
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
+ if ((attrFlags & ATTR_GLOBAL) &&
+ (!(attrFlags & ATTR_VALTOKEN) ||
+ nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
+ return true;
+ }
+ }
+ }
+
+ // If the given ID is referred by relation attribute then create an
+ // accessible for it.
+ nsAutoString id;
+ if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
+ return aDocument->IsDependentID(aContent->AsElement(), id);
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Return true if the SVG element should be accessible
+ */
+static bool MustSVGElementBeAccessible(nsIContent* aContent) {
+ // https://w3c.github.io/svg-aam/#include_elements
+ for (nsIContent* childElm = aContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsAnyOfSVGElements(nsGkAtoms::title, nsGkAtoms::desc)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Used by XULMap.h to map both menupopup and popup elements
+ */
+#ifdef MOZ_XUL
+Accessible* CreateMenupopupAccessible(Element* aElement, Accessible* aContext) {
+# ifdef MOZ_ACCESSIBILITY_ATK
+ // ATK considers this node to be redundant when within menubars, and it makes
+ // menu navigation with assistive technologies more difficult
+ // XXX In the future we will should this for consistency across the
+ // nsIAccessible implementations on each platform for a consistent scripting
+ // environment, but then strip out redundant accessibles in the AccessibleWrap
+ // class for each platform.
+ nsIContent* parent = aElement->GetParent();
+ if (parent && parent->IsXULElement(nsGkAtoms::menu)) return nullptr;
+# endif
+
+ return new XULMenupopupAccessible(aElement, aContext->Document());
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible constructors
+
+static Accessible* New_HyperText(Element* aElement, Accessible* aContext) {
+ return new HyperTextAccessibleWrap(aElement, aContext->Document());
+}
+
+template <typename AccClass>
+static Accessible* New_HTMLDtOrDd(Element* aElement, Accessible* aContext) {
+ nsIContent* parent = aContext->GetContent();
+ if (parent->IsHTMLElement(nsGkAtoms::div)) {
+ // It is conforming in HTML to use a div to group dt/dd elements.
+ parent = parent->GetParent();
+ }
+
+ if (parent && parent->IsHTMLElement(nsGkAtoms::dl)) {
+ return new AccClass(aElement, aContext->Document());
+ }
+
+ return nullptr;
+}
+
+/**
+ * Cached value of the PREF_ACCESSIBILITY_FORCE_DISABLED preference.
+ */
+static int32_t sPlatformDisabledState = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+// Markup maps array.
+
+#define Attr(name, value) \
+ { nsGkAtoms::name, nsGkAtoms::value }
+
+#define AttrFromDOM(name, DOMAttrName) \
+ { nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName }
+
+#define AttrFromDOMIf(name, DOMAttrName, DOMAttrValue) \
+ { nsGkAtoms::name, nullptr, nsGkAtoms::DOMAttrName, nsGkAtoms::DOMAttrValue }
+
+#define MARKUPMAP(atom, new_func, r, ...) \
+ {nsGkAtoms::atom, new_func, static_cast<a11y::role>(r), {__VA_ARGS__}},
+
+static const HTMLMarkupMapInfo sHTMLMarkupMapList[] = {
+#include "MarkupMap.h"
+};
+
+#undef MARKUPMAP
+
+#ifdef MOZ_XUL
+# define XULMAP(atom, ...) {nsGkAtoms::atom, __VA_ARGS__},
+
+# define XULMAP_TYPE(atom, new_type) \
+ XULMAP(atom, [](Element* aElement, Accessible* aContext) -> Accessible* { \
+ return new new_type(aElement, aContext->Document()); \
+ })
+
+static const XULMarkupMapInfo sXULMarkupMapList[] = {
+# include "XULMap.h"
+};
+
+# undef XULMAP_TYPE
+# undef XULMAP
+#endif
+
+#undef Attr
+#undef AttrFromDOM
+#undef AttrFromDOMIf
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService
+////////////////////////////////////////////////////////////////////////////////
+
+nsAccessibilityService* nsAccessibilityService::gAccessibilityService = nullptr;
+ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
+xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible =
+ nullptr;
+uint32_t nsAccessibilityService::gConsumers = 0;
+
+nsAccessibilityService::nsAccessibilityService()
+ : DocManager(),
+ FocusManager(),
+ mHTMLMarkupMap(ArrayLength(sHTMLMarkupMapList))
+#ifdef MOZ_XUL
+ ,
+ mXULMarkupMap(ArrayLength(sXULMarkupMapList))
+#endif
+{
+}
+
+nsAccessibilityService::~nsAccessibilityService() {
+ NS_ASSERTION(IsShutdown(), "Accessibility wasn't shutdown!");
+ gAccessibilityService = nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIListenerChangeListener
+
+NS_IMETHODIMP
+nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges) {
+ uint32_t targetCount;
+ nsresult rv = aEventChanges->GetLength(&targetCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < targetCount; i++) {
+ nsCOMPtr<nsIEventListenerChange> change =
+ do_QueryElementAt(aEventChanges, i);
+
+ RefPtr<EventTarget> target;
+ change->GetTarget(getter_AddRefs(target));
+ nsCOMPtr<nsIContent> node(do_QueryInterface(target));
+ if (!node || !node->IsHTMLElement()) {
+ continue;
+ }
+
+ uint32_t changeCount;
+ change->GetCountOfEventListenerChangesAffectingAccessibility(&changeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (changeCount) {
+ Document* ownerDoc = node->OwnerDoc();
+ DocAccessible* document = GetExistingDocAccessible(ownerDoc);
+
+ if (document) {
+ Accessible* acc = document->GetAccessible(node);
+ if (!acc && nsCoreUtils::HasClickListener(node)) {
+ // Create an accessible for a inaccessible element having click event
+ // handler.
+ document->ContentInserted(node, node->GetNextSibling());
+ } else if (acc && acc->IsHTMLLink() && !acc->AsHTMLLink()->IsLinked()) {
+ // Notify of a LINKED state change if an HTML link gets a click
+ // listener but does not have an href attribute.
+ RefPtr<AccEvent> linkedChangeEvent =
+ new AccStateChangeEvent(acc, states::LINKED);
+ document->FireDelayedEvent(linkedChangeEvent);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService, DocManager, nsIObserver,
+ nsIListenerChangeListener,
+ nsISelectionListener) // from SelectionManager
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIObserver
+
+NS_IMETHODIMP
+nsAccessibilityService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+void nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode) {
+ Document* documentNode = aTargetNode->GetUncomposedDoc();
+ if (documentNode) {
+ DocAccessible* document = GetDocAccessible(documentNode);
+ if (document) document->SetAnchorJump(aTargetNode);
+ }
+}
+
+void nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent,
+ Accessible* aTarget) {
+ nsEventShell::FireEvent(aEvent, aTarget);
+}
+
+void nsAccessibilityService::NotifyOfImageSizeAvailable(
+ mozilla::PresShell* aPresShell, nsIContent* aContent) {
+ // If the size of an image is initially unknown, it will have the invisible
+ // state (and a 0 width and height), causing it to be ignored by some screen
+ // readers. Fire a state change event to update any client caches.
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aContent);
+ // The accessible may not be an ImageAccessible if this was previously a
+ // broken image with an alt attribute. In that case, do nothing; the
+ // accessible will be recreated if this becomes a valid image.
+ if (accessible && accessible->IsImage()) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::INVISIBLE, false);
+ document->FireDelayedEvent(event);
+ }
+ }
+}
+
+Accessible* nsAccessibilityService::GetRootDocumentAccessible(
+ PresShell* aPresShell, bool aCanCreate) {
+ PresShell* presShell = aPresShell;
+ Document* documentNode = aPresShell->GetDocument();
+ if (documentNode) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell());
+ if (treeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
+ if (treeItem != rootTreeItem) {
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootTreeItem));
+ presShell = docShell->GetPresShell();
+ }
+
+ return aCanCreate ? GetDocAccessible(presShell)
+ : presShell->GetDocAccessible();
+ }
+ }
+ return nullptr;
+}
+
+#ifdef XP_WIN
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsIContent> > > sPendingPlugins;
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsITimer> > > sPluginTimers;
+
+class PluginTimerCallBack final : public nsITimerCallback, public nsINamed {
+ ~PluginTimerCallBack() {}
+
+ public:
+ explicit PluginTimerCallBack(nsIContent* aContent) : mContent(aContent) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) final {
+ if (!mContent->IsInUncomposedDoc()) return NS_OK;
+
+ PresShell* presShell = mContent->OwnerDoc()->GetPresShell();
+ if (presShell) {
+ DocAccessible* doc = presShell->GetDocAccessible();
+ if (doc) {
+ // Make sure that if we created an accessible for the plugin that wasn't
+ // a plugin accessible we remove it before creating the right
+ // accessible.
+ doc->RecreateAccessible(mContent);
+ sPluginTimers->RemoveElement(aTimer);
+ return NS_OK;
+ }
+ }
+
+ // We couldn't get a doc accessible so presumably the document went away.
+ // In this case don't leak our ref to the content or timer.
+ sPendingPlugins->RemoveElement(mContent);
+ sPluginTimers->RemoveElement(aTimer);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetName(nsACString& aName) final {
+ aName.AssignLiteral("PluginTimerCallBack");
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+NS_IMPL_ISUPPORTS(PluginTimerCallBack, nsITimerCallback, nsINamed)
+#endif
+
+already_AddRefed<Accessible> nsAccessibilityService::CreatePluginAccessible(
+ nsPluginFrame* aFrame, nsIContent* aContent, Accessible* aContext) {
+ // nsPluginFrame means a plugin, so we need to use the accessibility support
+ // of the plugin.
+ if (aFrame->GetRect().IsEmpty()) return nullptr;
+
+#if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
+ RefPtr<nsNPAPIPluginInstance> pluginInstance = aFrame->GetPluginInstance();
+ if (pluginInstance) {
+# ifdef XP_WIN
+ if (!sPendingPlugins->Contains(aContent) &&
+ (Preferences::GetBool("accessibility.delay_plugins") ||
+ Compatibility::IsJAWS() || Compatibility::IsWE())) {
+ RefPtr<PluginTimerCallBack> cb = new PluginTimerCallBack(aContent);
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithCallback(
+ getter_AddRefs(timer), cb,
+ Preferences::GetUint("accessibility.delay_plugin_time"),
+ nsITimer::TYPE_ONE_SHOT);
+ sPluginTimers->AppendElement(timer);
+ sPendingPlugins->AppendElement(aContent);
+ return nullptr;
+ }
+
+ // We need to remove aContent from the pending plugins here to avoid
+ // reentrancy. When the timer fires it calls
+ // DocAccessible::ContentInserted() which does the work async.
+ sPendingPlugins->RemoveElement(aContent);
+
+ // Note: pluginPort will be null if windowless.
+ HWND pluginPort = nullptr;
+ aFrame->GetPluginPort(&pluginPort);
+
+ RefPtr<Accessible> accessible = new HTMLWin32ObjectOwnerAccessible(
+ aContent, aContext->Document(), pluginPort);
+ return accessible.forget();
+
+# elif MOZ_ACCESSIBILITY_ATK
+ if (!AtkSocketAccessible::gCanEmbed) return nullptr;
+
+ // Note this calls into the plugin, so unexpected things may happen and
+ // aFrame may go away.
+ nsCString plugId;
+ nsresult rv = pluginInstance->GetValueFromPlugin(
+ NPPVpluginNativeAccessibleAtkPlugId, &plugId);
+ if (NS_SUCCEEDED(rv) && !plugId.IsEmpty()) {
+ RefPtr<AtkSocketAccessible> socketAccessible =
+ new AtkSocketAccessible(aContent, aContext->Document(), plugId);
+
+ return socketAccessible.forget();
+ }
+# endif
+ }
+#endif
+
+ return nullptr;
+}
+
+void nsAccessibilityService::DeckPanelSwitched(PresShell* aPresShell,
+ nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame,
+ nsIFrame* aCurrentBoxFrame) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (!document) {
+ return;
+ }
+ // A deck with an Accessible is a tabpanels element.
+ const bool isTabPanels = document->HasAccessible(aDeckNode);
+ MOZ_ASSERT(!isTabPanels || aDeckNode->IsXULElement(nsGkAtoms::tabpanels),
+ "A deck with an Accessible should be a tabpanels element");
+
+ if (aPrevBoxFrame) {
+ nsIContent* panelNode = aPrevBoxFrame->GetContent();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "deck panel unselected");
+ logging::Node("container", panelNode);
+ logging::Node("content", aDeckNode);
+ logging::MsgEnd();
+ }
+#endif
+ if (isTabPanels) {
+ // Tabpanels are accessible even when not selected.
+ if (Accessible* acc = document->GetAccessible(panelNode)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(acc, states::OFFSCREEN, true);
+ document->FireDelayedEvent(event);
+ }
+ } else {
+ document->ContentRemoved(panelNode);
+ }
+ }
+
+ if (aCurrentBoxFrame) {
+ nsIContent* panelNode = aCurrentBoxFrame->GetContent();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "deck panel selected");
+ logging::Node("container", panelNode);
+ logging::Node("content", aDeckNode);
+ logging::MsgEnd();
+ }
+#endif
+ if (isTabPanels) {
+ // Tabpanels are accessible even when not selected, so we don't have to
+ // insert an Accessible.
+ if (Accessible* acc = document->GetAccessible(panelNode)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(acc, states::OFFSCREEN, false);
+ document->FireDelayedEvent(event);
+ }
+ } else {
+ document->ContentInserted(panelNode, panelNode->GetNextSibling());
+ }
+ }
+}
+
+void nsAccessibilityService::ContentRangeInserted(PresShell* aPresShell,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "content inserted; doc: %p", document);
+ logging::Node("container", aStartChild->GetParent());
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ logging::Node("content", child);
+ }
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+
+ if (document) {
+ document->ContentInserted(aStartChild, aEndChild);
+ }
+}
+
+void nsAccessibilityService::ContentRemoved(PresShell* aPresShell,
+ nsIContent* aChildNode) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "content removed; doc: %p", document);
+ logging::Node("container node", aChildNode->GetFlattenedTreeParent());
+ logging::Node("content node", aChildNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ if (document) {
+ document->ContentRemoved(aChildNode);
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+}
+
+void nsAccessibilityService::UpdateText(PresShell* aPresShell,
+ nsIContent* aContent) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) document->UpdateText(aContent);
+}
+
+void nsAccessibilityService::TreeViewChanged(PresShell* aPresShell,
+ nsIContent* aContent,
+ nsITreeView* aView) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aContent);
+ if (accessible) {
+ XULTreeAccessible* treeAcc = accessible->AsXULTree();
+ if (treeAcc) treeAcc->TreeViewChanged(aView);
+ }
+ }
+}
+
+void nsAccessibilityService::RangeValueChanged(PresShell* aPresShell,
+ nsIContent* aContent) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aContent);
+ if (accessible) {
+ document->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
+ accessible);
+ }
+ }
+}
+
+void nsAccessibilityService::UpdateListBullet(PresShell* aPresShell,
+ nsIContent* aHTMLListItemContent,
+ bool aHasBullet) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aHTMLListItemContent);
+ if (accessible) {
+ HTMLLIAccessible* listItem = accessible->AsHTMLListItem();
+ if (listItem) listItem->UpdateBullet(aHasBullet);
+ }
+ }
+}
+
+void nsAccessibilityService::UpdateImageMap(nsImageFrame* aImageFrame) {
+ PresShell* presShell = aImageFrame->PresShell();
+ DocAccessible* document = GetDocAccessible(presShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aImageFrame->GetContent());
+ if (accessible) {
+ HTMLImageMapAccessible* imageMap = accessible->AsImageMap();
+ if (imageMap) {
+ imageMap->UpdateChildAreas();
+ return;
+ }
+
+ // If image map was initialized after we created an accessible (that'll
+ // be an image accessible) then recreate it.
+ RecreateAccessible(presShell, aImageFrame->GetContent());
+ }
+ }
+}
+
+void nsAccessibilityService::UpdateLabelValue(PresShell* aPresShell,
+ nsIContent* aLabelElm,
+ const nsString& aNewValue) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aLabelElm);
+ if (accessible) {
+ XULLabelAccessible* xulLabel = accessible->AsXULLabel();
+ NS_ASSERTION(xulLabel,
+ "UpdateLabelValue was called for wrong accessible!");
+ if (xulLabel) xulLabel->UpdateLabelValue(aNewValue);
+ }
+ }
+}
+
+void nsAccessibilityService::PresShellActivated(PresShell* aPresShell) {
+ DocAccessible* document = aPresShell->GetDocAccessible();
+ if (document) {
+ RootAccessible* rootDocument = document->RootAccessible();
+ NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!");
+ if (rootDocument) rootDocument->DocumentActivated(document);
+ }
+}
+
+void nsAccessibilityService::RecreateAccessible(PresShell* aPresShell,
+ nsIContent* aContent) {
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) document->RecreateAccessible(aContent);
+}
+
+void nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString) {
+#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ case roles::geckoRole: \
+ aString.AssignLiteral(stringRole); \
+ return;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+#undef ROLE
+}
+
+void nsAccessibilityService::GetStringStates(uint32_t aState,
+ uint32_t aExtraState,
+ nsISupports** aStringStates) {
+ RefPtr<DOMStringList> stringStates =
+ GetStringStates(nsAccUtils::To64State(aState, aExtraState));
+
+ // unknown state
+ if (!stringStates->Length()) {
+ stringStates->Add(u"unknown"_ns);
+ }
+
+ stringStates.forget(aStringStates);
+}
+
+already_AddRefed<DOMStringList> nsAccessibilityService::GetStringStates(
+ uint64_t aStates) const {
+ RefPtr<DOMStringList> stringStates = new DOMStringList();
+
+ if (aStates & states::UNAVAILABLE) {
+ stringStates->Add(u"unavailable"_ns);
+ }
+ if (aStates & states::SELECTED) {
+ stringStates->Add(u"selected"_ns);
+ }
+ if (aStates & states::FOCUSED) {
+ stringStates->Add(u"focused"_ns);
+ }
+ if (aStates & states::PRESSED) {
+ stringStates->Add(u"pressed"_ns);
+ }
+ if (aStates & states::CHECKED) {
+ stringStates->Add(u"checked"_ns);
+ }
+ if (aStates & states::MIXED) {
+ stringStates->Add(u"mixed"_ns);
+ }
+ if (aStates & states::READONLY) {
+ stringStates->Add(u"readonly"_ns);
+ }
+ if (aStates & states::HOTTRACKED) {
+ stringStates->Add(u"hottracked"_ns);
+ }
+ if (aStates & states::DEFAULT) {
+ stringStates->Add(u"default"_ns);
+ }
+ if (aStates & states::EXPANDED) {
+ stringStates->Add(u"expanded"_ns);
+ }
+ if (aStates & states::COLLAPSED) {
+ stringStates->Add(u"collapsed"_ns);
+ }
+ if (aStates & states::BUSY) {
+ stringStates->Add(u"busy"_ns);
+ }
+ if (aStates & states::FLOATING) {
+ stringStates->Add(u"floating"_ns);
+ }
+ if (aStates & states::ANIMATED) {
+ stringStates->Add(u"animated"_ns);
+ }
+ if (aStates & states::INVISIBLE) {
+ stringStates->Add(u"invisible"_ns);
+ }
+ if (aStates & states::OFFSCREEN) {
+ stringStates->Add(u"offscreen"_ns);
+ }
+ if (aStates & states::SIZEABLE) {
+ stringStates->Add(u"sizeable"_ns);
+ }
+ if (aStates & states::MOVEABLE) {
+ stringStates->Add(u"moveable"_ns);
+ }
+ if (aStates & states::SELFVOICING) {
+ stringStates->Add(u"selfvoicing"_ns);
+ }
+ if (aStates & states::FOCUSABLE) {
+ stringStates->Add(u"focusable"_ns);
+ }
+ if (aStates & states::SELECTABLE) {
+ stringStates->Add(u"selectable"_ns);
+ }
+ if (aStates & states::LINKED) {
+ stringStates->Add(u"linked"_ns);
+ }
+ if (aStates & states::TRAVERSED) {
+ stringStates->Add(u"traversed"_ns);
+ }
+ if (aStates & states::MULTISELECTABLE) {
+ stringStates->Add(u"multiselectable"_ns);
+ }
+ if (aStates & states::EXTSELECTABLE) {
+ stringStates->Add(u"extselectable"_ns);
+ }
+ if (aStates & states::PROTECTED) {
+ stringStates->Add(u"protected"_ns);
+ }
+ if (aStates & states::HASPOPUP) {
+ stringStates->Add(u"haspopup"_ns);
+ }
+ if (aStates & states::REQUIRED) {
+ stringStates->Add(u"required"_ns);
+ }
+ if (aStates & states::ALERT) {
+ stringStates->Add(u"alert"_ns);
+ }
+ if (aStates & states::INVALID) {
+ stringStates->Add(u"invalid"_ns);
+ }
+ if (aStates & states::CHECKABLE) {
+ stringStates->Add(u"checkable"_ns);
+ }
+ if (aStates & states::SUPPORTS_AUTOCOMPLETION) {
+ stringStates->Add(u"autocompletion"_ns);
+ }
+ if (aStates & states::DEFUNCT) {
+ stringStates->Add(u"defunct"_ns);
+ }
+ if (aStates & states::SELECTABLE_TEXT) {
+ stringStates->Add(u"selectable text"_ns);
+ }
+ if (aStates & states::EDITABLE) {
+ stringStates->Add(u"editable"_ns);
+ }
+ if (aStates & states::ACTIVE) {
+ stringStates->Add(u"active"_ns);
+ }
+ if (aStates & states::MODAL) {
+ stringStates->Add(u"modal"_ns);
+ }
+ if (aStates & states::MULTI_LINE) {
+ stringStates->Add(u"multi line"_ns);
+ }
+ if (aStates & states::HORIZONTAL) {
+ stringStates->Add(u"horizontal"_ns);
+ }
+ if (aStates & states::OPAQUE1) {
+ stringStates->Add(u"opaque"_ns);
+ }
+ if (aStates & states::SINGLE_LINE) {
+ stringStates->Add(u"single line"_ns);
+ }
+ if (aStates & states::TRANSIENT) {
+ stringStates->Add(u"transient"_ns);
+ }
+ if (aStates & states::VERTICAL) {
+ stringStates->Add(u"vertical"_ns);
+ }
+ if (aStates & states::STALE) {
+ stringStates->Add(u"stale"_ns);
+ }
+ if (aStates & states::ENABLED) {
+ stringStates->Add(u"enabled"_ns);
+ }
+ if (aStates & states::SENSITIVE) {
+ stringStates->Add(u"sensitive"_ns);
+ }
+ if (aStates & states::PINNED) {
+ stringStates->Add(u"pinned"_ns);
+ }
+ if (aStates & states::CURRENT) {
+ stringStates->Add(u"current"_ns);
+ }
+
+ return stringStates.forget();
+}
+
+void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
+ nsAString& aString) {
+ NS_ASSERTION(
+ nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
+ "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
+
+ if (aEventType >= ArrayLength(kEventTypeNames)) {
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+ aString.AssignASCII(kEventTypeNames[aEventType]);
+}
+
+void nsAccessibilityService::GetStringEventType(uint32_t aEventType,
+ nsACString& aString) {
+ MOZ_ASSERT(
+ nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
+ "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
+
+ if (aEventType >= ArrayLength(kEventTypeNames)) {
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+ aString = nsDependentCString(kEventTypeNames[aEventType]);
+}
+
+void nsAccessibilityService::GetStringRelationType(uint32_t aRelationType,
+ nsAString& aString) {
+ NS_ENSURE_TRUE_VOID(aRelationType <=
+ static_cast<uint32_t>(RelationType::LAST));
+
+#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
+ case RelationType::geckoType: \
+ aString.AssignLiteral(geckoTypeName); \
+ return;
+
+ RelationType relationType = static_cast<RelationType>(aRelationType);
+ switch (relationType) {
+#include "RelationTypeMap.h"
+ default:
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+#undef RELATIONTYPE
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService public
+
+Accessible* nsAccessibilityService::CreateAccessible(nsINode* aNode,
+ Accessible* aContext,
+ bool* aIsSubtreeHidden) {
+ MOZ_ASSERT(aContext, "No context provided");
+ MOZ_ASSERT(aNode, "No node to create an accessible for");
+ MOZ_ASSERT(gConsumers, "No creation after shutdown");
+
+ if (aIsSubtreeHidden) *aIsSubtreeHidden = false;
+
+ DocAccessible* document = aContext->Document();
+ MOZ_ASSERT(!document->GetAccessible(aNode),
+ "We already have an accessible for this node.");
+
+ if (aNode->IsDocument()) {
+ // If it's document node then ask accessible document loader for
+ // document accessible, otherwise return null.
+ return GetDocAccessible(aNode->AsDocument());
+ }
+
+ // We have a content node.
+ if (!aNode->GetComposedDoc()) {
+ NS_WARNING("Creating accessible for node with no document");
+ return nullptr;
+ }
+
+ if (aNode->OwnerDoc() != document->DocumentNode()) {
+ NS_ERROR("Creating accessible for wrong document");
+ return nullptr;
+ }
+
+ if (!aNode->IsContent()) return nullptr;
+
+ nsIContent* content = aNode->AsContent();
+ if (aria::HasDefinedARIAHidden(content)) {
+ if (aIsSubtreeHidden) {
+ *aIsSubtreeHidden = true;
+ }
+ return nullptr;
+ }
+
+ // Check frame and its visibility. Note, hidden frame allows visible
+ // elements in subtree.
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame || !frame->StyleVisibility()->IsVisible()) {
+ // display:contents element doesn't have a frame, but retains the semantics.
+ // All its children are unaffected.
+ if (nsCoreUtils::IsDisplayContents(content)) {
+ const HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func) {
+ RefPtr<Accessible> newAcc =
+ markupMap->new_func(content->AsElement(), aContext);
+ if (newAcc) {
+ document->BindToDocument(newAcc,
+ aria::GetRoleMap(content->AsElement()));
+ }
+ return newAcc;
+ }
+ return nullptr;
+ }
+
+ if (aIsSubtreeHidden && !frame) *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ if (frame->GetContent() != content) {
+ // Not the main content for this frame. This happens because <area>
+ // elements return the image frame as their primary frame. The main content
+ // for the image frame is the image content. If the frame is not an image
+ // frame or the node is not an area element then null is returned.
+ // This setup will change when bug 135040 is fixed. Make sure we don't
+ // create area accessible here. Hopefully assertion below will handle that.
+
+#ifdef DEBUG
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ NS_ASSERTION(imageFrame && content->IsHTMLElement(nsGkAtoms::area),
+ "Unknown case of not main content for the frame!");
+#endif
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ NS_ASSERTION(!imageFrame || !content->IsHTMLElement(nsGkAtoms::area),
+ "Image map manages the area accessible creation!");
+#endif
+
+ // Attempt to create an accessible based on what we know.
+ RefPtr<Accessible> newAcc;
+
+ // Create accessible for visible text frames.
+ if (content->IsText()) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(
+ 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
+ nsIFrame::TrailingWhitespace::DontTrim);
+ // Ignore not rendered text nodes and whitespace text nodes between table
+ // cells.
+ if (text.mString.IsEmpty() ||
+ (aContext->IsTableRow() &&
+ nsCoreUtils::IsWhitespaceString(text.mString))) {
+ if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ newAcc = CreateAccessibleByFrameType(frame, content, aContext);
+ document->BindToDocument(newAcc, nullptr);
+ newAcc->AsTextLeaf()->SetText(text.mString);
+ return newAcc;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::map)) {
+ // Create hyper text accessible for HTML map if it is used to group links
+ // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML
+ // map rect is empty then it is used for links grouping. Otherwise it should
+ // be used in conjunction with HTML image element and in this case we don't
+ // create any accessible for it and don't walk into it. The accessibles for
+ // HTML area (HTMLAreaAccessible) the map contains are attached as
+ // children of the appropriate accessible for HTML image
+ // (ImageAccessible).
+ if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
+ .IsEmpty()) {
+ if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ newAcc = new HyperTextAccessibleWrap(content, document);
+ document->BindToDocument(newAcc, aria::GetRoleMap(content->AsElement()));
+ return newAcc;
+ }
+
+ const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
+
+ // If the element is focusable or global ARIA attribute is applied to it or
+ // it is referenced by ARIA relationship then treat role="presentation" on
+ // the element as the role is not there.
+ if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) ||
+ roleMapEntry->Is(nsGkAtoms::none))) {
+ if (!MustBeAccessible(content, document)) return nullptr;
+
+ roleMapEntry = nullptr;
+ }
+
+ if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
+ bool isARIATablePart = roleMapEntry && (roleMapEntry->accTypes &
+ (eTableCell | eTableRow | eTable));
+
+ if (!isARIATablePart || frame->AccessibleType() == eHTMLTableCellType ||
+ frame->AccessibleType() == eHTMLTableRowType ||
+ frame->AccessibleType() == eHTMLTableType ||
+ // We should always use OuterDocAccessible for OuterDocs, even for
+ // ARIA table roles.
+ frame->AccessibleType() == eOuterDocType) {
+ // Prefer to use markup to decide if and what kind of accessible to
+ // create,
+ const HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content->AsElement(), aContext);
+
+ if (!newAcc) // try by frame accessible type.
+ newAcc = CreateAccessibleByFrameType(frame, content, aContext);
+ }
+
+ // In case of ARIA grid or table use table-specific classes if it's not
+ // native table based.
+ if (isARIATablePart && (!newAcc || newAcc->IsGenericHyperText())) {
+ if ((roleMapEntry->accTypes & eTableCell)) {
+ if (aContext->IsTableRow())
+ newAcc = new ARIAGridCellAccessibleWrap(content, document);
+
+ } else if (roleMapEntry->IsOfType(eTableRow)) {
+ if (aContext->IsTable() ||
+ (aContext->Parent() && aContext->Parent()->IsTable())) {
+ newAcc = new ARIARowAccessible(content, document);
+ }
+
+ } else if (roleMapEntry->IsOfType(eTable)) {
+ newAcc = new ARIAGridAccessibleWrap(content, document);
+ }
+ }
+
+ // If table has strong ARIA role then all table descendants shouldn't
+ // expose their native roles.
+ if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) {
+ if (frame->AccessibleType() == eHTMLTableRowType) {
+ const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
+ if (!contextRoleMap->IsOfType(eTable))
+ roleMapEntry = &aria::gEmptyRoleMap;
+
+ } else if (frame->AccessibleType() == eHTMLTableCellType &&
+ aContext->ARIARoleMap() == &aria::gEmptyRoleMap) {
+ roleMapEntry = &aria::gEmptyRoleMap;
+
+ } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::li,
+ nsGkAtoms::dd) ||
+ frame->AccessibleType() == eHTMLLiType) {
+ const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
+ if (!contextRoleMap->IsOfType(eList))
+ roleMapEntry = &aria::gEmptyRoleMap;
+ }
+ }
+ }
+
+ // XUL accessibles.
+ if (!newAcc && content->IsXULElement()) {
+ // No accessible for not selected deck panel and its children.
+ if (!aContext->IsXULTabpanels()) {
+ nsDeckFrame* deckFrame = do_QueryFrame(frame->GetParent());
+ if (deckFrame && deckFrame->GetSelectedBox() != frame) {
+ if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+ }
+
+#ifdef MOZ_XUL
+ // Prefer to use XUL to decide if and what kind of accessible to create.
+ const XULMarkupMapInfo* xulMap =
+ mXULMarkupMap.Get(content->NodeInfo()->NameAtom());
+ if (xulMap && xulMap->new_func) {
+ newAcc = xulMap->new_func(content->AsElement(), aContext);
+ }
+#endif
+
+ // Any XUL box can be used as tabpanel, make sure we create a proper
+ // accessible for it.
+ if (!newAcc && aContext->IsXULTabpanels() &&
+ content->GetParent() == aContext->GetContent()) {
+ LayoutFrameType frameType = frame->Type();
+ if (frameType == LayoutFrameType::Box ||
+ frameType == LayoutFrameType::Scroll) {
+ newAcc = new XULTabpanelAccessible(content, document);
+ }
+ }
+ }
+
+ if (!newAcc) {
+ if (content->IsSVGElement()) {
+ SVGGeometryFrame* geometryFrame = do_QueryFrame(frame);
+ if (geometryFrame && MustSVGElementBeAccessible(content)) {
+ // A graphic elements: rect, circle, ellipse, line, path, polygon,
+ // polyline and image. A 'use' and 'text' graphic elements require
+ // special support.
+ newAcc = new EnumRoleAccessible<roles::GRAPHIC>(content, document);
+ } else if (content->IsSVGElement(nsGkAtoms::text)) {
+ newAcc = new HyperTextAccessibleWrap(content->AsElement(), document);
+ } else if (content->IsSVGElement(nsGkAtoms::svg)) {
+ newAcc = new EnumRoleAccessible<roles::DIAGRAM>(content, document);
+ } else if (content->IsSVGElement(nsGkAtoms::g) &&
+ MustSVGElementBeAccessible(content)) {
+ newAcc = new EnumRoleAccessible<roles::GROUPING>(content, document);
+ }
+
+ } else if (content->IsMathMLElement()) {
+ const HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content->AsElement(), aContext);
+
+ // Fall back to text when encountering Content MathML.
+ if (!newAcc && !content->IsAnyOfMathMLElements(
+ nsGkAtoms::annotation_, nsGkAtoms::annotation_xml_,
+ nsGkAtoms::mpadded_, nsGkAtoms::mphantom_,
+ nsGkAtoms::maligngroup_, nsGkAtoms::malignmark_,
+ nsGkAtoms::mspace_, nsGkAtoms::semantics_)) {
+ newAcc = new HyperTextAccessible(content, document);
+ }
+ }
+ }
+
+ // If no accessible, see if we need to create a generic accessible because
+ // of some property that makes this object interesting
+ // We don't do this for <body>, <html>, <window>, <dialog> etc. which
+ // correspond to the doc accessible and will be created in any case
+ if (!newAcc && !content->IsHTMLElement(nsGkAtoms::body) &&
+ content->GetParent() &&
+ (roleMapEntry || MustBeAccessible(content, document) ||
+ (content->IsHTMLElement() && nsCoreUtils::HasClickListener(content)))) {
+ // This content is focusable or has an interesting dynamic content
+ // accessibility property. If it's interesting we need it in the
+ // accessibility hierarchy so that events or other accessibles can point to
+ // it, or so that it can hold a state, etc.
+ if (content->IsHTMLElement()) {
+ // Interesting HTML container which may have selectable text and/or
+ // embedded objects
+ newAcc = new HyperTextAccessibleWrap(content, document);
+ } else { // XUL, SVG, MathML etc.
+ // Interesting generic non-HTML container
+ newAcc = new AccessibleWrap(content, document);
+ }
+ }
+
+ if (newAcc) {
+ document->BindToDocument(newAcc, roleMapEntry);
+ }
+ return newAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService private
+
+bool nsAccessibilityService::Init() {
+ // Initialize accessible document manager.
+ if (!DocManager::Init()) return false;
+
+ // Add observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return false;
+
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+
+#if defined(XP_WIN)
+ // This information needs to be initialized before the observer fires.
+ if (XRE_IsParentProcess()) {
+ Compatibility::Init();
+ }
+#endif // defined(XP_WIN)
+
+ // Subscribe to EventListenerService.
+ nsCOMPtr<nsIEventListenerService> eventListenerService =
+ do_GetService("@mozilla.org/eventlistenerservice;1");
+ if (!eventListenerService) return false;
+
+ eventListenerService->AddListenerChangeListener(this);
+
+ for (uint32_t i = 0; i < ArrayLength(sHTMLMarkupMapList); i++)
+ mHTMLMarkupMap.Put(sHTMLMarkupMapList[i].tag, &sHTMLMarkupMapList[i]);
+
+#ifdef MOZ_XUL
+ for (uint32_t i = 0; i < ArrayLength(sXULMarkupMapList); i++)
+ mXULMarkupMap.Put(sXULMarkupMapList[i].tag, &sXULMarkupMapList[i]);
+#endif
+
+#ifdef A11Y_LOG
+ logging::CheckEnv();
+#endif
+
+ gAccessibilityService = this;
+ NS_ADDREF(gAccessibilityService); // will release in Shutdown()
+
+ if (XRE_IsParentProcess()) {
+ gApplicationAccessible = new ApplicationAccessibleWrap();
+ } else {
+#if defined(XP_WIN)
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ // If we were instantiated by the chrome process, GetMsaaID() will return
+ // a non-zero value and we may safely continue with initialization.
+ if (!contentChild->GetMsaaID()) {
+ // Since we were not instantiated by chrome, we need to synchronously
+ // obtain a MSAA content process id.
+ contentChild->SendGetA11yContentId();
+ }
+
+ gApplicationAccessible = new ApplicationAccessibleWrap();
+#else
+ gApplicationAccessible = new ApplicationAccessible();
+#endif // defined(XP_WIN)
+ }
+
+ NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
+ gApplicationAccessible->Init();
+
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Accessibility,
+ "Active"_ns);
+
+#ifdef XP_WIN
+ sPendingPlugins = new nsTArray<nsCOMPtr<nsIContent> >;
+ sPluginTimers = new nsTArray<nsCOMPtr<nsITimer> >;
+#endif
+
+ // Now its safe to start platform accessibility.
+ if (XRE_IsParentProcess()) PlatformInit();
+
+ statistics::A11yInitialized();
+
+ static const char16_t kInitIndicator[] = {'1', 0};
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown",
+ kInitIndicator);
+
+ return true;
+}
+
+void nsAccessibilityService::Shutdown() {
+ // Application is going to be closed, shutdown accessibility and mark
+ // accessibility service as shutdown to prevent calls of its methods.
+ // Don't null accessibility service static member at this point to be safe
+ // if someone will try to operate with it.
+
+ MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
+ UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
+
+ // Remove observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ // Stop accessible document loader.
+ DocManager::Shutdown();
+
+ SelectionManager::Shutdown();
+
+#ifdef XP_WIN
+ sPendingPlugins = nullptr;
+
+ uint32_t timerCount = sPluginTimers->Length();
+ for (uint32_t i = 0; i < timerCount; i++)
+ sPluginTimers->ElementAt(i)->Cancel();
+
+ sPluginTimers = nullptr;
+#endif
+
+ if (XRE_IsParentProcess()) PlatformShutdown();
+
+ gApplicationAccessible->Shutdown();
+ NS_RELEASE(gApplicationAccessible);
+ gApplicationAccessible = nullptr;
+
+ NS_IF_RELEASE(gXPCApplicationAccessible);
+ gXPCApplicationAccessible = nullptr;
+
+ NS_RELEASE(gAccessibilityService);
+ gAccessibilityService = nullptr;
+
+ if (observerService) {
+ static const char16_t kShutdownIndicator[] = {'0', 0};
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown",
+ kShutdownIndicator);
+ }
+}
+
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame* aFrame,
+ nsIContent* aContent,
+ Accessible* aContext) {
+ DocAccessible* document = aContext->Document();
+
+ RefPtr<Accessible> newAcc;
+ switch (aFrame->AccessibleType()) {
+ case eNoType:
+ return nullptr;
+ case eHTMLBRType:
+ newAcc = new HTMLBRAccessible(aContent, document);
+ break;
+ case eHTMLButtonType:
+ newAcc = new HTMLButtonAccessible(aContent, document);
+ break;
+ case eHTMLCanvasType:
+ newAcc = new HTMLCanvasAccessible(aContent, document);
+ break;
+ case eHTMLCaptionType:
+ if (aContext->IsTable() &&
+ aContext->GetContent() == aContent->GetParent()) {
+ newAcc = new HTMLCaptionAccessible(aContent, document);
+ }
+ break;
+ case eHTMLCheckboxType:
+ newAcc = new CheckboxAccessible(aContent, document);
+ break;
+ case eHTMLComboboxType:
+ newAcc = new HTMLComboboxAccessible(aContent, document);
+ break;
+ case eHTMLFileInputType:
+ newAcc = new HTMLFileInputAccessible(aContent, document);
+ break;
+ case eHTMLGroupboxType:
+ newAcc = new HTMLGroupboxAccessible(aContent, document);
+ break;
+ case eHTMLHRType:
+ newAcc = new HTMLHRAccessible(aContent, document);
+ break;
+ case eHTMLImageMapType:
+ newAcc = new HTMLImageMapAccessible(aContent, document);
+ break;
+ case eHTMLLiType:
+ if (aContext->IsList() &&
+ aContext->GetContent() == aContent->GetParent()) {
+ newAcc = new HTMLLIAccessible(aContent, document);
+ } else {
+ // Otherwise create a generic text accessible to avoid text jamming.
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ }
+ break;
+ case eHTMLSelectListType:
+ newAcc = new HTMLSelectListAccessible(aContent, document);
+ break;
+ case eHTMLMediaType:
+ newAcc = new EnumRoleAccessible<roles::GROUPING>(aContent, document);
+ break;
+ case eHTMLRadioButtonType:
+ newAcc = new HTMLRadioButtonAccessible(aContent, document);
+ break;
+ case eHTMLRangeType:
+ newAcc = new HTMLRangeAccessible(aContent, document);
+ break;
+ case eHTMLSpinnerType:
+ newAcc = new HTMLSpinnerAccessible(aContent, document);
+ break;
+ case eHTMLTableType:
+ if (aContent->IsHTMLElement(nsGkAtoms::table))
+ newAcc = new HTMLTableAccessibleWrap(aContent, document);
+ else
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ break;
+ case eHTMLTableCellType:
+ // Accessible HTML table cell should be a child of accessible HTML table
+ // or its row (CSS HTML tables are polite to the used markup at
+ // certain degree).
+ // Otherwise create a generic text accessible to avoid text jamming
+ // when reading by AT.
+ if (aContext->IsHTMLTableRow() || aContext->IsHTMLTable())
+ newAcc = new HTMLTableCellAccessibleWrap(aContent, document);
+ else
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ break;
+
+ case eHTMLTableRowType: {
+ // Accessible HTML table row may be a child of tbody/tfoot/thead of
+ // accessible HTML table or a direct child of accessible of HTML table.
+ Accessible* table = aContext->IsTable() ? aContext : nullptr;
+ if (!table && aContext->Parent() && aContext->Parent()->IsTable())
+ table = aContext->Parent();
+
+ if (table) {
+ nsIContent* parentContent =
+ aContent->GetParentOrShadowHostNode()->AsContent();
+ nsIFrame* parentFrame = nullptr;
+ if (parentContent) {
+ parentFrame = parentContent->GetPrimaryFrame();
+ if (!parentFrame || !parentFrame->IsTableWrapperFrame()) {
+ parentContent =
+ parentContent->GetParentOrShadowHostNode()->AsContent();
+ if (parentContent) {
+ parentFrame = parentContent->GetPrimaryFrame();
+ }
+ }
+ }
+
+ if (parentFrame && parentFrame->IsTableWrapperFrame() &&
+ table->GetContent() == parentContent) {
+ newAcc = new HTMLTableRowAccessible(aContent, document);
+ }
+ }
+ break;
+ }
+ case eHTMLTextFieldType:
+ newAcc = new HTMLTextFieldAccessible(aContent, document);
+ break;
+ case eHyperTextType: {
+ if (aContext->IsTable() || aContext->IsTableRow()) {
+ // This is some generic hyperText, for example a block frame element
+ // inserted between a table and table row. Treat it as presentational.
+ return nullptr;
+ }
+
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd,
+ nsGkAtoms::div, nsGkAtoms::thead,
+ nsGkAtoms::tfoot, nsGkAtoms::tbody)) {
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ }
+ break;
+ }
+ case eImageType:
+ newAcc = new ImageAccessibleWrap(aContent, document);
+ break;
+ case eOuterDocType:
+ newAcc = new OuterDocAccessible(aContent, document);
+ break;
+ case ePluginType: {
+ nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
+ newAcc = CreatePluginAccessible(pluginFrame, aContent, aContext);
+ break;
+ }
+ case eTextLeafType:
+ newAcc = new TextLeafAccessibleWrap(aContent, document);
+ break;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ return newAcc.forget();
+}
+
+void nsAccessibilityService::MarkupAttributes(
+ const nsIContent* aContent, nsIPersistentProperties* aAttributes) const {
+ const mozilla::a11y::HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(aContent->NodeInfo()->NameAtom());
+ if (!markupMap) return;
+
+ for (uint32_t i = 0; i < ArrayLength(markupMap->attrs); i++) {
+ const MarkupAttrInfo* info = markupMap->attrs + i;
+ if (!info->name) break;
+
+ if (info->DOMAttrName) {
+ if (info->DOMAttrValue) {
+ if (aContent->IsElement() && aContent->AsElement()->AttrValueIs(
+ kNameSpaceID_None, info->DOMAttrName,
+ info->DOMAttrValue, eCaseMatters)) {
+ nsAccUtils::SetAccAttr(aAttributes, info->name, info->DOMAttrValue);
+ }
+ continue;
+ }
+
+ nsAutoString value;
+
+ if (aContent->IsElement()) {
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, info->DOMAttrName,
+ value);
+ }
+
+ if (!value.IsEmpty())
+ nsAccUtils::SetAccAttr(aAttributes, info->name, value);
+
+ continue;
+ }
+
+ nsAccUtils::SetAccAttr(aAttributes, info->name, info->value);
+ }
+}
+
+Accessible* nsAccessibilityService::AddNativeRootAccessible(
+ void* aAtkAccessible) {
+#ifdef MOZ_ACCESSIBILITY_ATK
+ ApplicationAccessible* applicationAcc = ApplicationAcc();
+ if (!applicationAcc) return nullptr;
+
+ GtkWindowAccessible* nativeWnd =
+ new GtkWindowAccessible(static_cast<AtkObject*>(aAtkAccessible));
+
+ if (applicationAcc->AppendChild(nativeWnd)) return nativeWnd;
+#endif
+
+ return nullptr;
+}
+
+void nsAccessibilityService::RemoveNativeRootAccessible(
+ Accessible* aAccessible) {
+#ifdef MOZ_ACCESSIBILITY_ATK
+ ApplicationAccessible* applicationAcc = ApplicationAcc();
+
+ if (applicationAcc) applicationAcc->RemoveChild(aAccessible);
+#endif
+}
+
+bool nsAccessibilityService::HasAccessible(nsINode* aDOMNode) {
+ if (!aDOMNode) return false;
+
+ Document* document = aDOMNode->OwnerDoc();
+ if (!document) return false;
+
+ DocAccessible* docAcc = GetExistingDocAccessible(aDOMNode->OwnerDoc());
+ if (!docAcc) return false;
+
+ return docAcc->HasAccessible(aDOMNode);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService private (DON'T put methods here)
+
+void nsAccessibilityService::SetConsumers(uint32_t aConsumers, bool aNotify) {
+ if (gConsumers & aConsumers) {
+ return;
+ }
+
+ gConsumers |= aConsumers;
+ if (aNotify) {
+ NotifyOfConsumersChange();
+ }
+}
+
+void nsAccessibilityService::UnsetConsumers(uint32_t aConsumers) {
+ if (!(gConsumers & aConsumers)) {
+ return;
+ }
+
+ gConsumers &= ~aConsumers;
+ NotifyOfConsumersChange();
+}
+
+void nsAccessibilityService::GetConsumers(nsAString& aString) {
+ const char16_t* kJSONFmt =
+ u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
+ nsString json;
+ nsTextFormatter::ssprintf(json, kJSONFmt,
+ gConsumers & eXPCOM ? "true" : "false",
+ gConsumers & eMainProcess ? "true" : "false",
+ gConsumers & ePlatformAPI ? "true" : "false");
+ aString.Assign(json);
+}
+
+void nsAccessibilityService::NotifyOfConsumersChange() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ return;
+ }
+
+ nsAutoString consumers;
+ GetConsumers(consumers);
+ observerService->NotifyObservers(nullptr, "a11y-consumers-changed",
+ consumers.get());
+}
+
+nsAccessibilityService* GetOrCreateAccService(uint32_t aNewConsumer) {
+ // Do not initialize accessibility if it is force disabled.
+ if (PlatformDisabledState() == ePlatformIsDisabled) {
+ return nullptr;
+ }
+
+ if (!nsAccessibilityService::gAccessibilityService) {
+ RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
+ if (!service->Init()) {
+ service->Shutdown();
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
+ "Accessible service is not initialized.");
+ nsAccessibilityService::gAccessibilityService->SetConsumers(aNewConsumer);
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+void MaybeShutdownAccService(uint32_t aFormerConsumer) {
+ nsAccessibilityService* accService =
+ nsAccessibilityService::gAccessibilityService;
+
+ if (!accService || nsAccessibilityService::IsShutdown()) {
+ return;
+ }
+
+ // Still used by XPCOM
+ if (nsCoreUtils::AccEventObserversExist() ||
+ xpcAccessibilityService::IsInUse() || accService->HasXPCDocuments()) {
+ // In case the XPCOM flag was unset (possibly because of the shutdown
+ // timer in the xpcAccessibilityService) ensure it is still present. Note:
+ // this should be fixed when all the consumer logic is taken out as a
+ // separate class.
+ accService->SetConsumers(nsAccessibilityService::eXPCOM, false);
+
+ if (aFormerConsumer != nsAccessibilityService::eXPCOM) {
+ // Only unset non-XPCOM consumers.
+ accService->UnsetConsumers(aFormerConsumer);
+ }
+ return;
+ }
+
+ if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
+ accService->UnsetConsumers(aFormerConsumer);
+ } else {
+ accService
+ ->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Services
+////////////////////////////////////////////////////////////////////////////////
+
+namespace mozilla {
+namespace a11y {
+
+FocusManager* FocusMgr() {
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+SelectionManager* SelectionMgr() {
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+ApplicationAccessible* ApplicationAcc() {
+ return nsAccessibilityService::gApplicationAccessible;
+}
+
+xpcAccessibleApplication* XPCApplicationAcc() {
+ if (!nsAccessibilityService::gXPCApplicationAccessible &&
+ nsAccessibilityService::gApplicationAccessible) {
+ nsAccessibilityService::gXPCApplicationAccessible =
+ new xpcAccessibleApplication(
+ nsAccessibilityService::gApplicationAccessible);
+ NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible);
+ }
+
+ return nsAccessibilityService::gXPCApplicationAccessible;
+}
+
+EPlatformDisabledState PlatformDisabledState() {
+ static bool platformDisabledStateCached = false;
+ if (platformDisabledStateCached) {
+ return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
+ }
+
+ platformDisabledStateCached = true;
+ Preferences::RegisterCallback(PrefChanged, PREF_ACCESSIBILITY_FORCE_DISABLED);
+ return ReadPlatformDisabledState();
+}
+
+EPlatformDisabledState ReadPlatformDisabledState() {
+ sPlatformDisabledState =
+ Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED, 0);
+ if (sPlatformDisabledState < ePlatformIsForceEnabled) {
+ sPlatformDisabledState = ePlatformIsForceEnabled;
+ } else if (sPlatformDisabledState > ePlatformIsDisabled) {
+ sPlatformDisabledState = ePlatformIsDisabled;
+ }
+
+ return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
+}
+
+void PrefChanged(const char* aPref, void* aClosure) {
+ if (ReadPlatformDisabledState() == ePlatformIsDisabled) {
+ // Force shut down accessibility.
+ nsAccessibilityService* accService =
+ nsAccessibilityService::gAccessibilityService;
+ if (accService && !nsAccessibilityService::IsShutdown()) {
+ accService->Shutdown();
+ }
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h
new file mode 100644
index 0000000000..8458478535
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.h
@@ -0,0 +1,507 @@
+/* -*- 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 __nsAccessibilityService_h__
+#define __nsAccessibilityService_h__
+
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/a11y/FocusManager.h"
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/a11y/Role.h"
+#include "mozilla/a11y/SelectionManager.h"
+#include "mozilla/Preferences.h"
+
+#include "nsIContent.h"
+#include "nsIObserver.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIEventListenerService.h"
+#include "nsXULAppAPI.h"
+#include "xpcAccessibilityService.h"
+
+class nsImageFrame;
+class nsIArray;
+class nsIPersistentProperties;
+class nsPluginFrame;
+class nsITreeView;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace dom {
+class DOMStringList;
+class Element;
+} // namespace dom
+
+namespace a11y {
+
+class ApplicationAccessible;
+class xpcAccessibleApplication;
+
+/**
+ * Return focus manager.
+ */
+FocusManager* FocusMgr();
+
+/**
+ * Return selection manager.
+ */
+SelectionManager* SelectionMgr();
+
+/**
+ * Returns the application accessible.
+ */
+ApplicationAccessible* ApplicationAcc();
+xpcAccessibleApplication* XPCApplicationAcc();
+
+typedef Accessible*(New_Accessible)(mozilla::dom::Element* aElement,
+ Accessible* aContext);
+
+// These fields are not `nsStaticAtom* const` because MSVC doesn't like it.
+struct MarkupAttrInfo {
+ nsStaticAtom* name;
+ nsStaticAtom* value;
+
+ nsStaticAtom* DOMAttrName;
+ nsStaticAtom* DOMAttrValue;
+};
+
+struct HTMLMarkupMapInfo {
+ const nsStaticAtom* const tag;
+ New_Accessible* new_func;
+ a11y::role role;
+ MarkupAttrInfo attrs[4];
+};
+
+#ifdef MOZ_XUL
+struct XULMarkupMapInfo {
+ const nsStaticAtom* const tag;
+ New_Accessible* new_func;
+};
+#endif
+
+/**
+ * PREF_ACCESSIBILITY_FORCE_DISABLED preference change callback.
+ */
+void PrefChanged(const char* aPref, void* aClosure);
+
+/**
+ * Read and normalize PREF_ACCESSIBILITY_FORCE_DISABLED preference.
+ */
+EPlatformDisabledState ReadPlatformDisabledState();
+
+} // namespace a11y
+} // namespace mozilla
+
+class nsAccessibilityService final : public mozilla::a11y::DocManager,
+ public mozilla::a11y::FocusManager,
+ public mozilla::a11y::SelectionManager,
+ public nsIListenerChangeListener,
+ public nsIObserver {
+ public:
+ typedef mozilla::a11y::Accessible Accessible;
+ typedef mozilla::a11y::DocAccessible DocAccessible;
+
+ // nsIListenerChangeListener
+ NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
+
+ protected:
+ ~nsAccessibilityService();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ Accessible* GetRootDocumentAccessible(mozilla::PresShell* aPresShell,
+ bool aCanCreate);
+ already_AddRefed<Accessible> CreatePluginAccessible(nsPluginFrame* aFrame,
+ nsIContent* aContent,
+ Accessible* aContext);
+
+ /**
+ * Adds/remove ATK root accessible for gtk+ native window to/from children
+ * of the application accessible.
+ */
+ Accessible* AddNativeRootAccessible(void* aAtkAccessible);
+ void RemoveNativeRootAccessible(Accessible* aRootAccessible);
+
+ bool HasAccessible(nsINode* aDOMNode);
+
+ /**
+ * Get a string equivalent for an accessible role value.
+ */
+ void GetStringRole(uint32_t aRole, nsAString& aString);
+
+ /**
+ * Get a string equivalent for an accessible state/extra state.
+ */
+ already_AddRefed<mozilla::dom::DOMStringList> GetStringStates(
+ uint64_t aStates) const;
+ void GetStringStates(uint32_t aState, uint32_t aExtraState,
+ nsISupports** aStringStates);
+
+ /**
+ * Get a string equivalent for an accessible event value.
+ */
+ void GetStringEventType(uint32_t aEventType, nsAString& aString);
+
+ /**
+ * Get a string equivalent for an accessible event value.
+ */
+ void GetStringEventType(uint32_t aEventType, nsACString& aString);
+
+ /**
+ * Get a string equivalent for an accessible relation type.
+ */
+ void GetStringRelationType(uint32_t aRelationType, nsAString& aString);
+
+ // nsAccesibilityService
+ /**
+ * Notification used to update the accessible tree when deck panel is
+ * switched.
+ */
+ void DeckPanelSwitched(mozilla::PresShell* aPresShell, nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame);
+
+ /**
+ * Notification used to update the accessible tree when new content is
+ * inserted.
+ */
+ void ContentRangeInserted(mozilla::PresShell* aPresShell,
+ nsIContent* aStartChild, nsIContent* aEndChild);
+
+ /**
+ * Notification used to update the accessible tree when content is removed.
+ */
+ void ContentRemoved(mozilla::PresShell* aPresShell, nsIContent* aChild);
+
+ void UpdateText(mozilla::PresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update XUL:tree accessible tree when treeview is changed.
+ */
+ void TreeViewChanged(mozilla::PresShell* aPresShell, nsIContent* aContent,
+ nsITreeView* aView);
+
+ /**
+ * Notify of input@type="element" value change.
+ */
+ void RangeValueChanged(mozilla::PresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update list bullet accessible.
+ */
+ void UpdateListBullet(mozilla::PresShell* aPresShell,
+ nsIContent* aHTMLListItemContent, bool aHasBullet);
+
+ /**
+ * Update the image map.
+ */
+ void UpdateImageMap(nsImageFrame* aImageFrame);
+
+ /**
+ * Update the label accessible tree when rendered @value is changed.
+ */
+ void UpdateLabelValue(mozilla::PresShell* aPresShell, nsIContent* aLabelElm,
+ const nsString& aNewValue);
+
+ /**
+ * Notify accessibility that anchor jump has been accomplished to the given
+ * target. Used by layout.
+ */
+ void NotifyOfAnchorJumpTo(nsIContent* aTarget);
+
+ /**
+ * Notify that presshell is activated.
+ */
+ void PresShellActivated(mozilla::PresShell* aPresShell);
+
+ /**
+ * Recreate an accessible for the given content node in the presshell.
+ */
+ void RecreateAccessible(mozilla::PresShell* aPresShell, nsIContent* aContent);
+
+ void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
+
+ /**
+ * Notify accessibility that the size has become available for an image.
+ * This occurs when the size of an image is initially not known, but we've
+ * now loaded enough data to know the size.
+ * Called by layout.
+ */
+ void NotifyOfImageSizeAvailable(mozilla::PresShell* aPresShell,
+ nsIContent* aContent);
+
+ // nsAccessibiltiyService
+
+ /**
+ * Return true if accessibility service has been shutdown.
+ */
+ static bool IsShutdown() { return gConsumers == 0; };
+
+ /**
+ * Creates an accessible for the given DOM node.
+ *
+ * @param aNode [in] the given node
+ * @param aContext [in] context the accessible is created in
+ * @param aIsSubtreeHidden [out, optional] indicates whether the node's
+ * frame and its subtree is hidden
+ */
+ Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext,
+ bool* aIsSubtreeHidden = nullptr);
+
+ mozilla::a11y::role MarkupRole(const nsIContent* aContent) const {
+ const mozilla::a11y::HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(aContent->NodeInfo()->NameAtom());
+ return markupMap ? markupMap->role : mozilla::a11y::roles::NOTHING;
+ }
+
+ /**
+ * Return the associated value for a given attribute if
+ * it appears in the MarkupMap. Otherwise, it returns null.
+ */
+ nsStaticAtom* MarkupAttribute(const nsIContent* aContent,
+ nsStaticAtom* aAtom) const {
+ const mozilla::a11y::HTMLMarkupMapInfo* markupMap =
+ mHTMLMarkupMap.Get(aContent->NodeInfo()->NameAtom());
+ if (markupMap) {
+ for (size_t i = 0; i < mozilla::ArrayLength(markupMap->attrs); i++) {
+ const mozilla::a11y::MarkupAttrInfo* info = markupMap->attrs + i;
+ if (info->name == aAtom) {
+ return info->value;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ /**
+ * Set the object attribute defined by markup for the given element.
+ */
+ void MarkupAttributes(const nsIContent* aContent,
+ nsIPersistentProperties* aAttributes) const;
+
+ /**
+ * A list of possible accessibility service consumers. Accessibility service
+ * can only be shut down when there are no remaining consumers.
+ *
+ * eXPCOM - accessibility service is used by XPCOM.
+ *
+ * eMainProcess - accessibility service was started by main process in the
+ * content process.
+ *
+ * ePlatformAPI - accessibility service is used by the platform api in the
+ * main process.
+ */
+ enum ServiceConsumer {
+ eXPCOM = 1 << 0,
+ eMainProcess = 1 << 1,
+ ePlatformAPI = 1 << 2,
+ };
+
+ private:
+ // nsAccessibilityService creation is controlled by friend
+ // GetOrCreateAccService, keep constructors private.
+ nsAccessibilityService();
+ nsAccessibilityService(const nsAccessibilityService&);
+ nsAccessibilityService& operator=(const nsAccessibilityService&);
+
+ private:
+ /**
+ * Initialize accessibility service.
+ */
+ bool Init();
+
+ /**
+ * Shutdowns accessibility service.
+ */
+ void Shutdown();
+
+ /**
+ * Create an accessible whose type depends on the given frame.
+ */
+ already_AddRefed<Accessible> CreateAccessibleByFrameType(
+ nsIFrame* aFrame, nsIContent* aContent, Accessible* aContext);
+
+ /**
+ * Notify observers about change of the accessibility service's consumers.
+ */
+ void NotifyOfConsumersChange();
+
+ /**
+ * Get a JSON string representing the accessibility service consumers.
+ */
+ void GetConsumers(nsAString& aString);
+
+ /**
+ * Set accessibility service consumers.
+ */
+ void SetConsumers(uint32_t aConsumers, bool aNotify = true);
+
+ /**
+ * Unset accessibility service consumers.
+ */
+ void UnsetConsumers(uint32_t aConsumers);
+
+ /**
+ * Reference for accessibility service instance.
+ */
+ static nsAccessibilityService* gAccessibilityService;
+
+ /**
+ * Reference for application accessible instance.
+ */
+ static mozilla::a11y::ApplicationAccessible* gApplicationAccessible;
+ static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible;
+
+ /**
+ * Contains a set of accessibility service consumers.
+ */
+ static uint32_t gConsumers;
+
+ nsDataHashtable<nsPtrHashKey<const nsAtom>,
+ const mozilla::a11y::HTMLMarkupMapInfo*>
+ mHTMLMarkupMap;
+#ifdef MOZ_XUL
+ nsDataHashtable<nsPtrHashKey<const nsAtom>,
+ const mozilla::a11y::XULMarkupMapInfo*>
+ mXULMarkupMap;
+#endif
+
+ friend nsAccessibilityService* GetAccService();
+ friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
+ friend void MaybeShutdownAccService(uint32_t);
+ friend void mozilla::a11y::PrefChanged(const char*, void*);
+ friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
+ friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr();
+ friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
+ friend mozilla::a11y::xpcAccessibleApplication*
+ mozilla::a11y::XPCApplicationAcc();
+ friend class xpcAccessibilityService;
+};
+
+/**
+ * Return the accessibility service instance. (Handy global function)
+ */
+inline nsAccessibilityService* GetAccService() {
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+/**
+ * Return accessibility service instance; creating one if necessary.
+ */
+nsAccessibilityService* GetOrCreateAccService(
+ uint32_t aNewConsumer = nsAccessibilityService::ePlatformAPI);
+
+/**
+ * Shutdown accessibility service if needed.
+ */
+void MaybeShutdownAccService(uint32_t aFormerConsumer);
+
+/**
+ * Return true if we're in a content process and not B2G.
+ */
+inline bool IPCAccessibilityActive() { return XRE_IsContentProcess(); }
+
+/**
+ * Map nsIAccessibleEvents constants to strings. Used by
+ * nsAccessibilityService::GetStringEventType() method.
+ */
+static const char kEventTypeNames[][40] = {
+ "unknown", //
+ "show", // EVENT_SHOW
+ "hide", // EVENT_HIDE
+ "reorder", // EVENT_REORDER
+ "active decendent change", // EVENT_ACTIVE_DECENDENT_CHANGED
+ "focus", // EVENT_FOCUS
+ "state change", // EVENT_STATE_CHANGE
+ "location change", // EVENT_LOCATION_CHANGE
+ "name changed", // EVENT_NAME_CHANGE
+ "description change", // EVENT_DESCRIPTION_CHANGE
+ "value change", // EVENT_VALUE_CHANGE
+ "help change", // EVENT_HELP_CHANGE
+ "default action change", // EVENT_DEFACTION_CHANGE
+ "action change", // EVENT_ACTION_CHANGE
+ "accelerator change", // EVENT_ACCELERATOR_CHANGE
+ "selection", // EVENT_SELECTION
+ "selection add", // EVENT_SELECTION_ADD
+ "selection remove", // EVENT_SELECTION_REMOVE
+ "selection within", // EVENT_SELECTION_WITHIN
+ "alert", // EVENT_ALERT
+ "foreground", // EVENT_FOREGROUND
+ "menu start", // EVENT_MENU_START
+ "menu end", // EVENT_MENU_END
+ "menupopup start", // EVENT_MENUPOPUP_START
+ "menupopup end", // EVENT_MENUPOPUP_END
+ "capture start", // EVENT_CAPTURE_START
+ "capture end", // EVENT_CAPTURE_END
+ "movesize start", // EVENT_MOVESIZE_START
+ "movesize end", // EVENT_MOVESIZE_END
+ "contexthelp start", // EVENT_CONTEXTHELP_START
+ "contexthelp end", // EVENT_CONTEXTHELP_END
+ "dragdrop start", // EVENT_DRAGDROP_START
+ "dragdrop end", // EVENT_DRAGDROP_END
+ "dialog start", // EVENT_DIALOG_START
+ "dialog end", // EVENT_DIALOG_END
+ "scrolling start", // EVENT_SCROLLING_START
+ "scrolling end", // EVENT_SCROLLING_END
+ "minimize start", // EVENT_MINIMIZE_START
+ "minimize end", // EVENT_MINIMIZE_END
+ "document load complete", // EVENT_DOCUMENT_LOAD_COMPLETE
+ "document reload", // EVENT_DOCUMENT_RELOAD
+ "document load stopped", // EVENT_DOCUMENT_LOAD_STOPPED
+ "document attributes changed", // EVENT_DOCUMENT_ATTRIBUTES_CHANGED
+ "document content changed", // EVENT_DOCUMENT_CONTENT_CHANGED
+ "property changed", // EVENT_PROPERTY_CHANGED
+ "page changed", // EVENT_PAGE_CHANGED
+ "text attribute changed", // EVENT_TEXT_ATTRIBUTE_CHANGED
+ "text caret moved", // EVENT_TEXT_CARET_MOVED
+ "text changed", // EVENT_TEXT_CHANGED
+ "text inserted", // EVENT_TEXT_INSERTED
+ "text removed", // EVENT_TEXT_REMOVED
+ "text updated", // EVENT_TEXT_UPDATED
+ "text selection changed", // EVENT_TEXT_SELECTION_CHANGED
+ "visible data changed", // EVENT_VISIBLE_DATA_CHANGED
+ "text column changed", // EVENT_TEXT_COLUMN_CHANGED
+ "section changed", // EVENT_SECTION_CHANGED
+ "table caption changed", // EVENT_TABLE_CAPTION_CHANGED
+ "table model changed", // EVENT_TABLE_MODEL_CHANGED
+ "table summary changed", // EVENT_TABLE_SUMMARY_CHANGED
+ "table row description changed", // EVENT_TABLE_ROW_DESCRIPTION_CHANGED
+ "table row header changed", // EVENT_TABLE_ROW_HEADER_CHANGED
+ "table row insert", // EVENT_TABLE_ROW_INSERT
+ "table row delete", // EVENT_TABLE_ROW_DELETE
+ "table row reorder", // EVENT_TABLE_ROW_REORDER
+ "table column description changed", // EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED
+ "table column header changed", // EVENT_TABLE_COLUMN_HEADER_CHANGED
+ "table column insert", // EVENT_TABLE_COLUMN_INSERT
+ "table column delete", // EVENT_TABLE_COLUMN_DELETE
+ "table column reorder", // EVENT_TABLE_COLUMN_REORDER
+ "window activate", // EVENT_WINDOW_ACTIVATE
+ "window create", // EVENT_WINDOW_CREATE
+ "window deactivate", // EVENT_WINDOW_DEACTIVATE
+ "window destroy", // EVENT_WINDOW_DESTROY
+ "window maximize", // EVENT_WINDOW_MAXIMIZE
+ "window minimize", // EVENT_WINDOW_MINIMIZE
+ "window resize", // EVENT_WINDOW_RESIZE
+ "window restore", // EVENT_WINDOW_RESTORE
+ "hyperlink end index changed", // EVENT_HYPERLINK_END_INDEX_CHANGED
+ "hyperlink number of anchors changed", // EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
+ "hyperlink selected link changed", // EVENT_HYPERLINK_SELECTED_LINK_CHANGED
+ "hypertext link activated", // EVENT_HYPERTEXT_LINK_ACTIVATED
+ "hypertext link selected", // EVENT_HYPERTEXT_LINK_SELECTED
+ "hyperlink start index changed", // EVENT_HYPERLINK_START_INDEX_CHANGED
+ "hypertext changed", // EVENT_HYPERTEXT_CHANGED
+ "hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED
+ "object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
+ "virtual cursor changed", // EVENT_VIRTUALCURSOR_CHANGED
+ "text value change", // EVENT_TEXT_VALUE_CHANGE
+ "scrolling", // EVENT_SCROLLING
+ "announcement", // EVENT_ANNOUNCEMENT
+ "live region added", // EVENT_LIVE_REGION_ADDED
+ "live region removed", // EVENT_LIVE_REGION_REMOVED
+};
+
+#endif
diff --git a/accessible/base/nsAccessiblePivot.cpp b/accessible/base/nsAccessiblePivot.cpp
new file mode 100644
index 0000000000..456f6cbde3
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -0,0 +1,522 @@
+/* -*- 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 "nsAccessiblePivot.h"
+
+#include "HyperTextAccessible.h"
+#include "nsAccUtils.h"
+#include "States.h"
+#include "Pivot.h"
+#include "xpcAccessibleDocument.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+
+using namespace mozilla::a11y;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+
+/**
+ * An object that stores a given traversal rule during the pivot movement.
+ */
+class RuleCache : public PivotRule {
+ public:
+ explicit RuleCache(nsIAccessibleTraversalRule* aRule)
+ : mRule(aRule), mPreFilter{0} {}
+ ~RuleCache() {}
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ private:
+ nsCOMPtr<nsIAccessibleTraversalRule> mRule;
+ Maybe<nsTArray<uint32_t>> mAcceptRoles;
+ uint32_t mPreFilter;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessiblePivot
+
+nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot)
+ : mRoot(aRoot),
+ mModalRoot(nullptr),
+ mPosition(nullptr),
+ mStartOffset(-1),
+ mEndOffset(-1) {
+ NS_ASSERTION(aRoot, "A root accessible is required");
+}
+
+nsAccessiblePivot::~nsAccessiblePivot() {}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessiblePivot
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetRoot(nsIAccessible** aRoot) {
+ NS_ENSURE_ARG_POINTER(aRoot);
+
+ NS_IF_ADDREF(*aRoot = ToXPC(mRoot));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetPosition(nsIAccessible** aPosition) {
+ NS_ENSURE_ARG_POINTER(aPosition);
+
+ NS_IF_ADDREF(*aPosition = ToXPC(mPosition));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetPosition(nsIAccessible* aPosition) {
+ RefPtr<Accessible> position = nullptr;
+
+ if (aPosition) {
+ position = aPosition->ToInternalAccessible();
+ if (!position || !IsDescendantOf(position, GetActiveRoot()))
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Swap old position with new position, saves us an AddRef/Release.
+ mPosition.swap(position);
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = mEndOffset = -1;
+ NotifyOfPivotChange(position, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_NONE,
+ nsIAccessiblePivot::NO_BOUNDARY, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot) {
+ NS_ENSURE_ARG_POINTER(aModalRoot);
+
+ NS_IF_ADDREF(*aModalRoot = ToXPC(mModalRoot));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot) {
+ Accessible* modalRoot = nullptr;
+
+ if (aModalRoot) {
+ modalRoot = aModalRoot->ToInternalAccessible();
+ if (!modalRoot || !IsDescendantOf(modalRoot, mRoot))
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mModalRoot = modalRoot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset) {
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+
+ *aStartOffset = mStartOffset;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset) {
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+
+ *aEndOffset = mEndOffset;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
+ int32_t aStartOffset, int32_t aEndOffset,
+ bool aIsFromUserInput, uint8_t aArgc) {
+ NS_ENSURE_ARG(aTextAccessible);
+
+ // Check that start offset is smaller than end offset, and that if a value is
+ // smaller than 0, both should be -1.
+ NS_ENSURE_TRUE(
+ aStartOffset <= aEndOffset &&
+ (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
+ NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIAccessible> xpcAcc = do_QueryInterface(aTextAccessible);
+ NS_ENSURE_ARG(xpcAcc);
+
+ RefPtr<Accessible> acc = xpcAcc->ToInternalAccessible();
+ NS_ENSURE_ARG(acc);
+
+ HyperTextAccessible* position = acc->AsHyperText();
+ if (!position || !IsDescendantOf(position, GetActiveRoot()))
+ return NS_ERROR_INVALID_ARG;
+
+ // Make sure the given offsets don't exceed the character count.
+ if (aEndOffset > static_cast<int32_t>(position->CharacterCount()))
+ return NS_ERROR_FAILURE;
+
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = aStartOffset;
+ mEndOffset = aEndOffset;
+
+ mPosition.swap(acc);
+ NotifyOfPivotChange(acc, oldStart, oldEnd, nsIAccessiblePivot::REASON_NONE,
+ nsIAccessiblePivot::NO_BOUNDARY,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+// Traversal functions
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule,
+ nsIAccessible* aAnchor, bool aIncludeStart,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+ *aResult = false;
+
+ Accessible* anchor = mPosition;
+ if (aArgc > 0 && aAnchor) anchor = aAnchor->ToInternalAccessible();
+
+ if (anchor &&
+ (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
+ return NS_ERROR_NOT_IN_TREE;
+
+ Pivot pivot(GetActiveRoot());
+ RuleCache rule(aRule);
+ AccessibleOrProxy wrappedAnchor = AccessibleOrProxy(anchor);
+ AccessibleOrProxy newPos =
+ pivot.Next(wrappedAnchor, rule, (aArgc > 1) ? aIncludeStart : false);
+ if (!newPos.IsNull() && newPos.IsAccessible()) {
+ *aResult = MovePivotInternal(newPos.AsAccessible(),
+ nsIAccessiblePivot::REASON_NEXT,
+ (aArgc > 2) ? aIsFromUserInput : true);
+ } else if (newPos.IsProxy()) {
+ // we shouldn't ever end up with a proxy here, but if we do for some
+ // reason something is wrong. we should still return OK if we're null
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule,
+ nsIAccessible* aAnchor, bool aIncludeStart,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+ *aResult = false;
+
+ Accessible* anchor = mPosition;
+ if (aArgc > 0 && aAnchor) anchor = aAnchor->ToInternalAccessible();
+
+ if (anchor &&
+ (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
+ return NS_ERROR_NOT_IN_TREE;
+
+ Pivot pivot(GetActiveRoot());
+ RuleCache rule(aRule);
+ AccessibleOrProxy wrappedAnchor = AccessibleOrProxy(anchor);
+ AccessibleOrProxy newPos =
+ pivot.Prev(wrappedAnchor, rule, (aArgc > 1) ? aIncludeStart : false);
+ if (!newPos.IsNull() && newPos.IsAccessible()) {
+ *aResult = MovePivotInternal(newPos.AsAccessible(),
+ nsIAccessiblePivot::REASON_PREV,
+ (aArgc > 2) ? aIsFromUserInput : true);
+ } else if (newPos.IsProxy()) {
+ // we shouldn't ever end up with a proxy here, but if we do for some
+ // reason something is wrong. we should still return OK if we're null
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ Pivot pivot(GetActiveRoot());
+ RuleCache rule(aRule);
+ AccessibleOrProxy newPos = pivot.First(rule);
+ if (!newPos.IsNull() && newPos.IsAccessible()) {
+ *aResult = MovePivotInternal(newPos.AsAccessible(),
+ nsIAccessiblePivot::REASON_FIRST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ } else if (newPos.IsProxy()) {
+ // we shouldn't ever end up with a proxy here, but if we do for some
+ // reason something is wrong. we should still return OK if we're null
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ Pivot pivot(root);
+ RuleCache rule(aRule);
+ AccessibleOrProxy newPos = pivot.Last(rule);
+ if (!newPos.IsNull() && newPos.IsAccessible()) {
+ *aResult = MovePivotInternal(newPos.AsAccessible(),
+ nsIAccessiblePivot::REASON_LAST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ } else if (newPos.IsProxy()) {
+ // we shouldn't ever end up with a proxy here, but if we do for some
+ // reason something is wrong. we should still return OK if we're null
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+
+ *aResult = false;
+
+ Pivot pivot(GetActiveRoot());
+
+ int32_t newStart = mStartOffset, newEnd = mEndOffset;
+ Accessible* newPos = pivot.NextText(mPosition, &newStart, &newEnd, aBoundary);
+ if (newPos) {
+ *aResult = true;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ Accessible* oldPos = mPosition;
+ mStartOffset = newStart;
+ mEndOffset = newEnd;
+ mPosition = newPos;
+ NotifyOfPivotChange(oldPos, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_NEXT, aBoundary,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+
+ *aResult = false;
+
+ Pivot pivot(GetActiveRoot());
+
+ int32_t newStart = mStartOffset, newEnd = mEndOffset;
+ Accessible* newPos = pivot.PrevText(mPosition, &newStart, &newEnd, aBoundary);
+ if (newPos) {
+ *aResult = true;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ Accessible* oldPos = mPosition;
+ mStartOffset = newStart;
+ mEndOffset = newEnd;
+ mPosition = newPos;
+ NotifyOfPivotChange(oldPos, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_PREV, aBoundary,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule, int32_t aX,
+ int32_t aY, bool aIgnoreNoMatch,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aRule);
+
+ *aResult = false;
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ RuleCache rule(aRule);
+ Pivot pivot(root);
+
+ AccessibleOrProxy newPos = pivot.AtPoint(aX, aY, rule);
+ if ((!newPos.IsNull() && newPos.IsAccessible()) ||
+ !aIgnoreNoMatch) { // TODO does this need a proxy check?
+ *aResult = MovePivotInternal(newPos.AsAccessible(),
+ nsIAccessiblePivot::REASON_POINT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ } else if (newPos.IsProxy()) {
+ // we shouldn't ever end up with a proxy here, but if we do for some
+ // reason something is wrong. we should still return OK if we're null
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+// Observer functions
+
+NS_IMETHODIMP
+nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver) {
+ NS_ENSURE_ARG(aObserver);
+
+ mObservers.AppendElement(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver) {
+ NS_ENSURE_ARG(aObserver);
+
+ return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Private utility methods
+
+bool nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible,
+ Accessible* aAncestor) {
+ if (!aAncestor || aAncestor->IsDefunct()) return false;
+
+ // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
+ Accessible* accessible = aAccessible;
+ do {
+ if (accessible == aAncestor) return true;
+ } while ((accessible = accessible->Parent()));
+
+ return false;
+}
+
+bool nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
+ PivotMoveReason aReason,
+ bool aIsFromUserInput) {
+ RefPtr<Accessible> oldPosition = std::move(mPosition);
+ mPosition = aPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = mEndOffset = -1;
+
+ return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason,
+ nsIAccessiblePivot::NO_BOUNDARY, aIsFromUserInput);
+}
+
+bool nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason,
+ int16_t aBoundaryType,
+ bool aIsFromUserInput) {
+ if (aOldPosition == mPosition && aOldStart == mStartOffset &&
+ aOldEnd == mEndOffset)
+ return false;
+
+ nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition); // death grip
+ for (nsIAccessiblePivotObserver* obs : mObservers.ForwardRange()) {
+ obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, ToXPC(mPosition),
+ mStartOffset, mEndOffset, aReason, aBoundaryType,
+ aIsFromUserInput);
+ }
+
+ return true;
+}
+
+uint16_t RuleCache::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (!mAcceptRoles) {
+ mAcceptRoles.emplace();
+ DebugOnly<nsresult> rv = mRule->GetMatchRoles(*mAcceptRoles);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = mRule->GetPreFilter(&mPreFilter);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ if (mPreFilter) {
+ uint64_t state;
+ if (aAccOrProxy.IsAccessible()) {
+ state = aAccOrProxy.AsAccessible()->State();
+ } else {
+ state = aAccOrProxy.AsProxy()->State();
+ }
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_PLATFORM_PRUNED & mPreFilter) &&
+ nsAccUtils::MustPrune(aAccOrProxy)) {
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
+ (state & states::INVISIBLE))
+ return result;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
+ (state & states::OFFSCREEN))
+ return result;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
+ !(state & states::FOCUSABLE))
+ return result;
+
+ if (aAccOrProxy.IsAccessible() &&
+ (nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
+ !(state & states::OPAQUE1)) {
+ nsIFrame* frame = aAccOrProxy.AsAccessible()->GetFrame();
+ if (frame->StyleEffects()->mOpacity == 0.0f) {
+ return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ }
+ }
+
+ if (mAcceptRoles->Length() > 0) {
+ uint32_t accessibleRole = aAccOrProxy.Role();
+ bool matchesRole = false;
+ for (uint32_t idx = 0; idx < mAcceptRoles->Length(); idx++) {
+ matchesRole = mAcceptRoles->ElementAt(idx) == accessibleRole;
+ if (matchesRole) break;
+ }
+
+ if (!matchesRole) {
+ return result;
+ }
+ }
+
+ uint16_t matchResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ DebugOnly<nsresult> rv = mRule->Match(ToXPC(aAccOrProxy), &matchResult);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return result | matchResult;
+}
diff --git a/accessible/base/nsAccessiblePivot.h b/accessible/base/nsAccessiblePivot.h
new file mode 100644
index 0000000000..b9d7406c1a
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.h
@@ -0,0 +1,138 @@
+/* -*- 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 _nsAccessiblePivot_H_
+#define _nsAccessiblePivot_H_
+
+#include "nsIAccessiblePivot.h"
+
+#include "Accessible-inl.h"
+#include "nsTObserverArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class RuleCache;
+
+/**
+ * Class represents an accessible pivot.
+ */
+class nsAccessiblePivot final : public nsIAccessiblePivot {
+ public:
+ typedef mozilla::a11y::Accessible Accessible;
+
+ explicit nsAccessiblePivot(Accessible* aRoot);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAccessiblePivot,
+ nsIAccessiblePivot)
+
+ NS_DECL_NSIACCESSIBLEPIVOT
+
+ /*
+ * A simple getter for the pivot's position.
+ */
+ Accessible* Position() { return mPosition; }
+
+ int32_t StartOffset() { return mStartOffset; }
+
+ int32_t EndOffset() { return mEndOffset; }
+
+ private:
+ ~nsAccessiblePivot();
+ nsAccessiblePivot() = delete;
+ nsAccessiblePivot(const nsAccessiblePivot&) = delete;
+ void operator=(const nsAccessiblePivot&) = delete;
+
+ /*
+ * Notify all observers on a pivot change. Return true if it has changed and
+ * observers have been notified.
+ */
+ bool NotifyOfPivotChange(Accessible* aOldAccessible, int32_t aOldStart,
+ int32_t aOldEnd, PivotMoveReason aReason,
+ TextBoundaryType aBoundaryType,
+ bool aIsFromUserInput);
+
+ /*
+ * Check to see that the given accessible is a descendant of given ancestor
+ */
+ bool IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor);
+
+ /*
+ * Search in preorder for the first accessible to match the rule.
+ */
+ Accessible* SearchForward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent, nsresult* aResult);
+
+ /*
+ * Reverse search in preorder for the first accessible to match the rule.
+ */
+ Accessible* SearchBackward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent, nsresult* aResult);
+
+ /*
+ * Get the effective root for this pivot, either the true root or modal root.
+ */
+ Accessible* GetActiveRoot() const {
+ if (mModalRoot) {
+ NS_ENSURE_FALSE(mModalRoot->IsDefunct(), mRoot);
+ return mModalRoot;
+ }
+
+ return mRoot;
+ }
+
+ /*
+ * Update the pivot, and notify observers. Return true if it moved.
+ */
+ bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason,
+ bool aIsFromUserInput);
+
+ /*
+ * Get initial node we should start a search from with a given rule.
+ *
+ * When we do a move operation from one position to another,
+ * the initial position can be inside of a subtree that is ignored by
+ * the given rule. We need to step out of the ignored subtree and start
+ * the search from there.
+ *
+ */
+ Accessible* AdjustStartPosition(Accessible* aAccessible, RuleCache& aCache,
+ uint16_t* aFilterResult, nsresult* aResult);
+
+ /*
+ * The root accessible.
+ */
+ RefPtr<Accessible> mRoot;
+
+ /*
+ * The temporary modal root accessible.
+ */
+ RefPtr<Accessible> mModalRoot;
+
+ /*
+ * The current pivot position.
+ */
+ RefPtr<Accessible> mPosition;
+
+ /*
+ * The text start offset ofthe pivot.
+ */
+ int32_t mStartOffset;
+
+ /*
+ * The text end offset ofthe pivot.
+ */
+ int32_t mEndOffset;
+
+ /*
+ * The list of pivot-changed observers.
+ */
+ nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> > mObservers;
+};
+
+#endif
diff --git a/accessible/base/nsCoreUtils.cpp b/accessible/base/nsCoreUtils.cpp
new file mode 100644
index 0000000000..5d4abb5c1f
--- /dev/null
+++ b/accessible/base/nsCoreUtils.cpp
@@ -0,0 +1,587 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCoreUtils.h"
+
+#include "nsIAccessibleTypes.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "mozilla/dom/Document.h"
+#include "nsRange.h"
+#include "nsXULElement.h"
+#include "nsIDocShell.h"
+#include "nsIObserverService.h"
+#include "nsPresContext.h"
+#include "nsIScrollableFrame.h"
+#include "nsISelectionController.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TouchEvents.h"
+#include "nsView.h"
+#include "nsGkAtoms.h"
+
+#include "nsComponentManagerUtils.h"
+
+#include "XULTreeElement.h"
+#include "nsTreeColumns.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLLabelElement.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Selection.h"
+
+using namespace mozilla;
+
+using mozilla::dom::DOMRect;
+using mozilla::dom::Element;
+using mozilla::dom::Selection;
+using mozilla::dom::XULTreeElement;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsCoreUtils
+////////////////////////////////////////////////////////////////////////////////
+
+bool nsCoreUtils::IsLabelWithControl(nsIContent* aContent) {
+ dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromNode(aContent);
+ if (label && label->GetControl()) return true;
+
+ return false;
+}
+
+bool nsCoreUtils::HasClickListener(nsIContent* aContent) {
+ NS_ENSURE_TRUE(aContent, false);
+ EventListenerManager* listenerManager =
+ aContent->GetExistingListenerManager();
+
+ return listenerManager &&
+ (listenerManager->HasListenersFor(nsGkAtoms::onclick) ||
+ listenerManager->HasListenersFor(nsGkAtoms::onmousedown) ||
+ listenerManager->HasListenersFor(nsGkAtoms::onmouseup));
+}
+
+void nsCoreUtils::DispatchClickEvent(XULTreeElement* aTree, int32_t aRowIndex,
+ nsTreeColumn* aColumn,
+ const nsAString& aPseudoElt) {
+ RefPtr<dom::Element> tcElm = aTree->GetTreeBody();
+ if (!tcElm) return;
+
+ Document* document = tcElm->GetUncomposedDoc();
+ if (!document) return;
+
+ RefPtr<PresShell> presShell = document->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ // Ensure row is visible.
+ aTree->EnsureRowIsVisible(aRowIndex);
+
+ // Calculate x and y coordinates.
+ nsresult rv;
+ nsIntRect rect =
+ aTree->GetCoordsForCellItem(aRowIndex, aColumn, aPseudoElt, rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<DOMRect> treeBodyRect = tcElm->GetBoundingClientRect();
+ int32_t tcX = (int32_t)treeBodyRect->X();
+ int32_t tcY = (int32_t)treeBodyRect->Y();
+
+ // Dispatch mouse events.
+ AutoWeakFrame tcFrame = tcElm->GetPrimaryFrame();
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+
+ nsPoint offset;
+ nsCOMPtr<nsIWidget> rootWidget =
+ rootFrame->GetView()->GetNearestWidget(&offset);
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+
+ int32_t cnvdX = presContext->CSSPixelsToDevPixels(tcX + int32_t(rect.x) + 1) +
+ presContext->AppUnitsToDevPixels(offset.x);
+ int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + int32_t(rect.y) + 1) +
+ presContext->AppUnitsToDevPixels(offset.y);
+
+ // XUL is just desktop, so there is no real reason for senfing touch events.
+ DispatchMouseEvent(eMouseDown, cnvdX, cnvdY, tcElm, tcFrame, presShell,
+ rootWidget);
+
+ DispatchMouseEvent(eMouseUp, cnvdX, cnvdY, tcElm, tcFrame, presShell,
+ rootWidget);
+}
+
+void nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX,
+ int32_t aY, nsIContent* aContent,
+ nsIFrame* aFrame, PresShell* aPresShell,
+ nsIWidget* aRootWidget) {
+ WidgetMouseEvent event(true, aMessage, aRootWidget, WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eNormal);
+
+ event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
+
+ event.mClickCount = 1;
+ event.mButton = MouseButton::ePrimary;
+ event.mTime = PR_IntervalNow();
+ event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
+}
+
+void nsCoreUtils::DispatchTouchEvent(EventMessage aMessage, int32_t aX,
+ int32_t aY, nsIContent* aContent,
+ nsIFrame* aFrame, PresShell* aPresShell,
+ nsIWidget* aRootWidget) {
+ nsIDocShell* docShell = nullptr;
+ if (aPresShell->GetDocument()) {
+ docShell = aPresShell->GetDocument()->GetDocShell();
+ }
+ if (!dom::TouchEvent::PrefEnabled(docShell)) {
+ return;
+ }
+
+ WidgetTouchEvent event(true, aMessage, aRootWidget);
+
+ event.mTime = PR_IntervalNow();
+
+ // XXX: Touch has an identifier of -1 to hint that it is synthesized.
+ RefPtr<dom::Touch> t = new dom::Touch(-1, LayoutDeviceIntPoint(aX, aY),
+ LayoutDeviceIntPoint(1, 1), 0.0f, 1.0f);
+ t->SetTouchTarget(aContent);
+ event.mTouches.AppendElement(t);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
+}
+
+uint32_t nsCoreUtils::GetAccessKeyFor(nsIContent* aContent) {
+ // Accesskeys are registered by @accesskey attribute only. At first check
+ // whether it is presented on the given element to avoid the slow
+ // EventStateManager::GetRegisteredAccessKey() method.
+ if (!aContent->IsElement() ||
+ !aContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::accesskey))
+ return 0;
+
+ nsPresContext* presContext = aContent->OwnerDoc()->GetPresContext();
+ if (!presContext) return 0;
+
+ EventStateManager* esm = presContext->EventStateManager();
+ if (!esm) return 0;
+
+ return esm->GetRegisteredAccessKey(aContent->AsElement());
+}
+
+nsIContent* nsCoreUtils::GetDOMElementFor(nsIContent* aContent) {
+ if (aContent->IsElement()) return aContent;
+
+ if (aContent->IsText()) return aContent->GetFlattenedTreeParent();
+
+ return nullptr;
+}
+
+nsINode* nsCoreUtils::GetDOMNodeFromDOMPoint(nsINode* aNode, uint32_t aOffset) {
+ if (aNode && aNode->IsElement()) {
+ uint32_t childCount = aNode->GetChildCount();
+ NS_ASSERTION(aOffset <= childCount, "Wrong offset of the DOM point!");
+
+ // The offset can be after last child of container node that means DOM point
+ // is placed immediately after the last child. In this case use the DOM node
+ // from the given DOM point is used as result node.
+ if (aOffset != childCount) return aNode->GetChildAt_Deprecated(aOffset);
+ }
+
+ return aNode;
+}
+
+bool nsCoreUtils::IsAncestorOf(nsINode* aPossibleAncestorNode,
+ nsINode* aPossibleDescendantNode,
+ nsINode* aRootNode) {
+ NS_ENSURE_TRUE(aPossibleAncestorNode && aPossibleDescendantNode, false);
+
+ nsINode* parentNode = aPossibleDescendantNode;
+ while ((parentNode = parentNode->GetParentNode()) &&
+ parentNode != aRootNode) {
+ if (parentNode == aPossibleAncestorNode) return true;
+ }
+
+ return false;
+}
+
+nsresult nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ uint32_t aScrollType) {
+ ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+
+ return ScrollSubstringTo(aFrame, aRange, vertical, horizontal);
+}
+
+nsresult nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal) {
+ if (!aFrame || !aRange) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPresContext* presContext = aFrame->PresContext();
+
+ nsCOMPtr<nsISelectionController> selCon;
+ aFrame->GetSelectionController(presContext, getter_AddRefs(selCon));
+ NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+ RefPtr<dom::Selection> selection =
+ selCon->GetSelection(nsISelectionController::SELECTION_ACCESSIBILITY);
+
+ selection->RemoveAllRanges(IgnoreErrors());
+ selection->AddRangeAndSelectFramesAndNotifyListeners(*aRange, IgnoreErrors());
+
+ selection->ScrollIntoView(nsISelectionController::SELECTION_ANCHOR_REGION,
+ aVertical, aHorizontal,
+ Selection::SCROLL_SYNCHRONOUS);
+
+ selection->CollapseToStart(IgnoreErrors());
+
+ return NS_OK;
+}
+
+void nsCoreUtils::ScrollFrameToPoint(nsIFrame* aScrollableFrame,
+ nsIFrame* aFrame,
+ const nsIntPoint& aPoint) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollableFrame);
+ if (!scrollableFrame) return;
+
+ nsPoint point =
+ ToAppUnits(aPoint, aFrame->PresContext()->AppUnitsPerDevPixel());
+ nsRect frameRect = aFrame->GetScreenRectInAppUnits();
+ nsPoint deltaPoint = point - frameRect.TopLeft();
+
+ nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
+ scrollPoint -= deltaPoint;
+
+ scrollableFrame->ScrollTo(scrollPoint, ScrollMode::Instant);
+}
+
+void nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType,
+ ScrollAxis* aVertical,
+ ScrollAxis* aHorizontal) {
+ WhereToScroll whereY, whereX;
+ WhenToScroll whenY, whenX;
+ switch (aScrollType) {
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT:
+ whereY = kScrollToTop;
+ whenY = WhenToScroll::Always;
+ whereX = kScrollToLeft;
+ whenX = WhenToScroll::Always;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT:
+ whereY = kScrollToBottom;
+ whenY = WhenToScroll::Always;
+ whereX = kScrollToRight;
+ whenX = WhenToScroll::Always;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE:
+ whereY = kScrollToTop;
+ whenY = WhenToScroll::Always;
+ whereX = kScrollMinimum;
+ whenX = WhenToScroll::IfNotFullyVisible;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_EDGE:
+ whereY = kScrollToBottom;
+ whenY = WhenToScroll::Always;
+ whereX = kScrollMinimum;
+ whenX = WhenToScroll::IfNotFullyVisible;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_LEFT_EDGE:
+ whereY = kScrollMinimum;
+ whenY = WhenToScroll::IfNotFullyVisible;
+ whereX = kScrollToLeft;
+ whenX = WhenToScroll::Always;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_RIGHT_EDGE:
+ whereY = kScrollMinimum;
+ whenY = WhenToScroll::IfNotFullyVisible;
+ whereX = kScrollToRight;
+ whenX = WhenToScroll::Always;
+ break;
+ default:
+ whereY = kScrollMinimum;
+ whenY = WhenToScroll::IfNotFullyVisible;
+ whereX = kScrollMinimum;
+ whenX = WhenToScroll::IfNotFullyVisible;
+ }
+ *aVertical = ScrollAxis(whereY, whenY);
+ *aHorizontal = ScrollAxis(whereX, whenX);
+}
+
+nsIntPoint nsCoreUtils::GetScreenCoordsForWindow(nsINode* aNode) {
+ nsIntPoint coords(0, 0);
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShellFor(aNode));
+ if (!treeItem) return coords;
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner) return coords;
+
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ if (baseWindow)
+ baseWindow->GetPosition(&coords.x, &coords.y); // in device pixels
+
+ return coords;
+}
+
+already_AddRefed<nsIDocShell> nsCoreUtils::GetDocShellFor(nsINode* aNode) {
+ if (!aNode) return nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell = aNode->OwnerDoc()->GetDocShell();
+ return docShell.forget();
+}
+
+bool nsCoreUtils::IsRootDocument(Document* aDocument) {
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell for document!");
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ docShellTreeItem->GetInProcessParent(getter_AddRefs(parentTreeItem));
+
+ return !parentTreeItem;
+}
+
+bool nsCoreUtils::IsContentDocument(Document* aDocument) {
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell tree item for document!");
+
+ return (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent);
+}
+
+bool nsCoreUtils::IsTopLevelContentDocInProcess(Document* aDocumentNode) {
+ mozilla::dom::BrowsingContext* bc = aDocumentNode->GetBrowsingContext();
+ return bc->IsContent() && (
+ // Tab document.
+ bc->IsTop() ||
+ // Out-of-process iframe.
+ !bc->GetParent()->IsInProcess());
+}
+
+bool nsCoreUtils::IsErrorPage(Document* aDocument) {
+ nsIURI* uri = aDocument->GetDocumentURI();
+ if (!uri->SchemeIs("about")) {
+ return false;
+ }
+
+ nsAutoCString path;
+ uri->GetPathQueryRef(path);
+
+ constexpr auto neterror = "neterror"_ns;
+ constexpr auto certerror = "certerror"_ns;
+
+ return StringBeginsWith(path, neterror) || StringBeginsWith(path, certerror);
+}
+
+PresShell* nsCoreUtils::GetPresShellFor(nsINode* aNode) {
+ return aNode->OwnerDoc()->GetPresShell();
+}
+
+bool nsCoreUtils::GetID(nsIContent* aContent, nsAString& aID) {
+ return aContent->IsElement() &&
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aID);
+}
+
+bool nsCoreUtils::GetUIntAttr(nsIContent* aContent, nsAtom* aAttr,
+ int32_t* aUInt) {
+ nsAutoString value;
+ if (!aContent->IsElement()) {
+ return false;
+ }
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, aAttr, value);
+ if (!value.IsEmpty()) {
+ nsresult error = NS_OK;
+ int32_t integer = value.ToInteger(&error);
+ if (NS_SUCCEEDED(error) && integer > 0) {
+ *aUInt = integer;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsCoreUtils::GetLanguageFor(nsIContent* aContent, nsIContent* aRootContent,
+ nsAString& aLanguage) {
+ aLanguage.Truncate();
+
+ nsIContent* walkUp = aContent;
+ while (walkUp && walkUp != aRootContent &&
+ (!walkUp->IsElement() ||
+ !walkUp->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
+ aLanguage)))
+ walkUp = walkUp->GetParent();
+}
+
+XULTreeElement* nsCoreUtils::GetTree(nsIContent* aContent) {
+ // Find DOMNode's parents recursively until reach the <tree> tag
+ nsIContent* currentContent = aContent;
+ while (currentContent) {
+ if (currentContent->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ return XULTreeElement::FromNode(currentContent);
+ }
+ currentContent = currentContent->GetFlattenedTreeParent();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsTreeColumn> nsCoreUtils::GetFirstSensibleColumn(
+ XULTreeElement* aTree, FlushType aFlushType) {
+ if (!aTree) {
+ return nullptr;
+ }
+
+ RefPtr<nsTreeColumns> cols = aTree->GetColumns(aFlushType);
+ if (!cols) {
+ return nullptr;
+ }
+
+ RefPtr<nsTreeColumn> column = cols->GetFirstColumn();
+ if (column && IsColumnHidden(column)) return GetNextSensibleColumn(column);
+
+ return column.forget();
+}
+
+uint32_t nsCoreUtils::GetSensibleColumnCount(XULTreeElement* aTree) {
+ uint32_t count = 0;
+ if (!aTree) {
+ return count;
+ }
+
+ RefPtr<nsTreeColumns> cols = aTree->GetColumns();
+ if (!cols) {
+ return count;
+ }
+
+ nsTreeColumn* column = cols->GetFirstColumn();
+
+ while (column) {
+ if (!IsColumnHidden(column)) count++;
+
+ column = column->GetNext();
+ }
+
+ return count;
+}
+
+already_AddRefed<nsTreeColumn> nsCoreUtils::GetSensibleColumnAt(
+ XULTreeElement* aTree, uint32_t aIndex) {
+ if (!aTree) {
+ return nullptr;
+ }
+
+ uint32_t idx = aIndex;
+
+ nsCOMPtr<nsTreeColumn> column = GetFirstSensibleColumn(aTree);
+ while (column) {
+ if (idx == 0) return column.forget();
+
+ idx--;
+ column = GetNextSensibleColumn(column);
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsTreeColumn> nsCoreUtils::GetNextSensibleColumn(
+ nsTreeColumn* aColumn) {
+ if (!aColumn) {
+ return nullptr;
+ }
+
+ RefPtr<nsTreeColumn> nextColumn = aColumn->GetNext();
+
+ while (nextColumn && IsColumnHidden(nextColumn)) {
+ nextColumn = nextColumn->GetNext();
+ }
+
+ return nextColumn.forget();
+}
+
+already_AddRefed<nsTreeColumn> nsCoreUtils::GetPreviousSensibleColumn(
+ nsTreeColumn* aColumn) {
+ if (!aColumn) {
+ return nullptr;
+ }
+
+ RefPtr<nsTreeColumn> prevColumn = aColumn->GetPrevious();
+
+ while (prevColumn && IsColumnHidden(prevColumn)) {
+ prevColumn = prevColumn->GetPrevious();
+ }
+
+ return prevColumn.forget();
+}
+
+bool nsCoreUtils::IsColumnHidden(nsTreeColumn* aColumn) {
+ if (!aColumn) {
+ return false;
+ }
+
+ Element* element = aColumn->Element();
+ return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+void nsCoreUtils::ScrollTo(PresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType) {
+ ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+ aPresShell->ScrollContentIntoView(aContent, vertical, horizontal,
+ ScrollFlags::ScrollOverflowHidden);
+}
+
+bool nsCoreUtils::IsHTMLTableHeader(nsIContent* aContent) {
+ return aContent->NodeInfo()->Equals(nsGkAtoms::th) ||
+ (aContent->IsElement() &&
+ aContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scope));
+}
+
+bool nsCoreUtils::IsWhitespaceString(const nsAString& aString) {
+ nsAString::const_char_iterator iterBegin, iterEnd;
+
+ aString.BeginReading(iterBegin);
+ aString.EndReading(iterEnd);
+
+ while (iterBegin != iterEnd && IsWhitespace(*iterBegin)) ++iterBegin;
+
+ return iterBegin == iterEnd;
+}
+
+bool nsCoreUtils::AccEventObserversExist() {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ NS_ENSURE_TRUE(obsService, false);
+
+ nsCOMPtr<nsISimpleEnumerator> observers;
+ obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
+ getter_AddRefs(observers));
+ NS_ENSURE_TRUE(observers, false);
+
+ bool hasObservers = false;
+ observers->HasMoreElements(&hasObservers);
+
+ return hasObservers;
+}
+
+void nsCoreUtils::DispatchAccEvent(RefPtr<nsIAccessibleEvent> event) {
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ NS_ENSURE_TRUE_VOID(obsService);
+
+ obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
+}
+
+bool nsCoreUtils::IsDisplayContents(nsIContent* aContent) {
+ return aContent && aContent->IsElement() &&
+ aContent->AsElement()->IsDisplayContents();
+}
diff --git a/accessible/base/nsCoreUtils.h b/accessible/base/nsCoreUtils.h
new file mode 100644
index 0000000000..a61409eca5
--- /dev/null
+++ b/accessible/base/nsCoreUtils.h
@@ -0,0 +1,328 @@
+/* -*- 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 nsCoreUtils_h_
+#define nsCoreUtils_h_
+
+#include "mozilla/EventForwards.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIContent.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/PresShellForwards.h"
+
+#include "nsPoint.h"
+#include "nsTArray.h"
+
+class nsRange;
+class nsTreeColumn;
+class nsIFrame;
+class nsIDocShell;
+class nsIWidget;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class XULTreeElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Core utils.
+ */
+class nsCoreUtils {
+ public:
+ typedef mozilla::PresShell PresShell;
+ typedef mozilla::dom::Document Document;
+
+ /**
+ * Return true if the given node is a label of a control.
+ */
+ static bool IsLabelWithControl(nsIContent* aContent);
+
+ /**
+ * Return true if the given node has registered click, mousedown or mouseup
+ * event listeners.
+ */
+ static bool HasClickListener(nsIContent* aContent);
+
+ /**
+ * Dispatch click event to XUL tree cell.
+ *
+ * @param aTree [in] tree
+ * @param aRowIndex [in] row index
+ * @param aColumn [in] column object
+ * @param aPseudoElm [in] pseudo element inside the cell, see
+ * XULTreeElement for available values
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void DispatchClickEvent(mozilla::dom::XULTreeElement* aTree,
+ int32_t aRowIndex, nsTreeColumn* aColumn,
+ const nsAString& aPseudoElt = u""_ns);
+
+ /**
+ * Send mouse event to the given element.
+ *
+ * @param aMessage [in] an event message (see EventForwards.h)
+ * @param aX [in] x coordinate in dev pixels
+ * @param aY [in] y coordinate in dev pixels
+ * @param aContent [in] the element
+ * @param aFrame [in] frame of the element
+ * @param aPresShell [in] the presshell for the element
+ * @param aRootWidget [in] the root widget of the element
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void DispatchMouseEvent(mozilla::EventMessage aMessage, int32_t aX,
+ int32_t aY, nsIContent* aContent,
+ nsIFrame* aFrame, PresShell* aPresShell,
+ nsIWidget* aRootWidget);
+
+ /**
+ * Send a touch event with a single touch point to the given element.
+ *
+ * @param aMessage [in] an event message (see EventForwards.h)
+ * @param aX [in] x coordinate in dev pixels
+ * @param aY [in] y coordinate in dev pixels
+ * @param aContent [in] the element
+ * @param aFrame [in] frame of the element
+ * @param aPresShell [in] the presshell for the element
+ * @param aRootWidget [in] the root widget of the element
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void DispatchTouchEvent(mozilla::EventMessage aMessage, int32_t aX,
+ int32_t aY, nsIContent* aContent,
+ nsIFrame* aFrame, PresShell* aPresShell,
+ nsIWidget* aRootWidget);
+
+ /**
+ * Return an accesskey registered on the given element by
+ * EventStateManager or 0 if there is no registered accesskey.
+ *
+ * @param aContent - the given element.
+ */
+ static uint32_t GetAccessKeyFor(nsIContent* aContent);
+
+ /**
+ * Return DOM element related with the given node, i.e.
+ * a) itself if it is DOM element
+ * b) parent element if it is text node
+ * c) otherwise nullptr
+ *
+ * @param aNode [in] the given DOM node
+ */
+ static nsIContent* GetDOMElementFor(nsIContent* aContent);
+
+ /**
+ * Return DOM node for the given DOM point.
+ */
+ static nsINode* GetDOMNodeFromDOMPoint(nsINode* aNode, uint32_t aOffset);
+
+ /**
+ * Is the first passed in node an ancestor of the second?
+ * Note: A node is not considered to be the ancestor of itself.
+ *
+ * @param aPossibleAncestorNode [in] node to test for ancestor-ness of
+ * aPossibleDescendantNode
+ * @param aPossibleDescendantNode [in] node to test for descendant-ness of
+ * aPossibleAncestorNode
+ * @param aRootNode [in, optional] the root node that search
+ * search should be performed within
+ * @return true if aPossibleAncestorNode is an ancestor of
+ * aPossibleDescendantNode
+ */
+ static bool IsAncestorOf(nsINode* aPossibleAncestorNode,
+ nsINode* aPossibleDescendantNode,
+ nsINode* aRootNode = nullptr);
+
+ /**
+ * Helper method to scroll range into view, used for implementation of
+ * nsIAccessibleText::scrollSubstringTo().
+ *
+ * @param aFrame the frame for accessible the range belongs to.
+ * @param aRange the range to scroll to
+ * @param aScrollType the place a range should be scrolled to
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult ScrollSubstringTo(
+ nsIFrame* aFrame, nsRange* aRange, uint32_t aScrollType);
+
+ /** Helper method to scroll range into view, used for implementation of
+ * nsIAccessibleText::scrollSubstringTo[Point]().
+ *
+ * @param aFrame the frame for accessible the range belongs to.
+ * @param aRange the range to scroll to
+ * @param aVertical how to align vertically, specified in percents, and
+ * when.
+ * @param aHorizontal how to align horizontally, specified in percents,
+ * and when.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult ScrollSubstringTo(
+ nsIFrame* aFrame, nsRange* aRange, mozilla::ScrollAxis aVertical,
+ mozilla::ScrollAxis aHorizontal);
+
+ /**
+ * Scrolls the given frame to the point, used for implememntation of
+ * nsIAccessible::scrollToPoint and nsIAccessibleText::scrollSubstringToPoint.
+ *
+ * @param aScrollableFrame the scrollable frame
+ * @param aFrame the frame to scroll
+ * @param aPoint the point scroll to
+ */
+ static void ScrollFrameToPoint(nsIFrame* aScrollableFrame, nsIFrame* aFrame,
+ const nsIntPoint& aPoint);
+
+ /**
+ * Converts scroll type constant defined in nsIAccessibleScrollType to
+ * vertical and horizontal parameters.
+ */
+ static void ConvertScrollTypeToPercents(uint32_t aScrollType,
+ mozilla::ScrollAxis* aVertical,
+ mozilla::ScrollAxis* aHorizontal);
+
+ /**
+ * Returns coordinates in device pixels relative screen for the top level
+ * window.
+ *
+ * @param aNode the DOM node hosted in the window.
+ */
+ static nsIntPoint GetScreenCoordsForWindow(nsINode* aNode);
+
+ /**
+ * Return document shell for the given DOM node.
+ */
+ static already_AddRefed<nsIDocShell> GetDocShellFor(nsINode* aNode);
+
+ /**
+ * Return true if the given document is root document.
+ */
+ static bool IsRootDocument(Document* aDocument);
+
+ /**
+ * Return true if the given document is content document (not chrome).
+ */
+ static bool IsContentDocument(Document* aDocument);
+
+ /**
+ * Return true if the given document is a top level content document in this
+ * process.
+ * This will be true for tab documents and out-of-process iframe documents.
+ */
+ static bool IsTopLevelContentDocInProcess(Document* aDocumentNode);
+
+ /**
+ * Return true if the given document is an error page.
+ */
+ static bool IsErrorPage(Document* aDocument);
+
+ /**
+ * Return presShell for the document containing the given DOM node.
+ */
+ static PresShell* GetPresShellFor(nsINode* aNode);
+
+ /**
+ * Get the ID for an element, in some types of XML this may not be the ID
+ * attribute
+ * @param aContent Node to get the ID for
+ * @param aID Where to put ID string
+ * @return true if there is an ID set for this node
+ */
+ static bool GetID(nsIContent* aContent, nsAString& aID);
+
+ /**
+ * Convert attribute value of the given node to positive integer. If no
+ * attribute or wrong value then false is returned.
+ */
+ static bool GetUIntAttr(nsIContent* aContent, nsAtom* aAttr, int32_t* aUInt);
+
+ /**
+ * Returns language for the given node.
+ *
+ * @param aContent [in] the given node
+ * @param aRootContent [in] container of the given node
+ * @param aLanguage [out] language
+ */
+ static void GetLanguageFor(nsIContent* aContent, nsIContent* aRootContent,
+ nsAString& aLanguage);
+
+ /**
+ * Return tree from any levels DOMNode under the XUL tree.
+ */
+ static mozilla::dom::XULTreeElement* GetTree(nsIContent* aContent);
+
+ /**
+ * Return first sensible column for the given tree box object.
+ */
+ static already_AddRefed<nsTreeColumn> GetFirstSensibleColumn(
+ mozilla::dom::XULTreeElement* aTree,
+ mozilla::FlushType = mozilla::FlushType::Frames);
+
+ /**
+ * Return sensible columns count for the given tree box object.
+ */
+ static uint32_t GetSensibleColumnCount(mozilla::dom::XULTreeElement* aTree);
+
+ /**
+ * Return sensible column at the given index for the given tree box object.
+ */
+ static already_AddRefed<nsTreeColumn> GetSensibleColumnAt(
+ mozilla::dom::XULTreeElement* aTree, uint32_t aIndex);
+
+ /**
+ * Return next sensible column for the given column.
+ */
+ static already_AddRefed<nsTreeColumn> GetNextSensibleColumn(
+ nsTreeColumn* aColumn);
+
+ /**
+ * Return previous sensible column for the given column.
+ */
+ static already_AddRefed<nsTreeColumn> GetPreviousSensibleColumn(
+ nsTreeColumn* aColumn);
+
+ /**
+ * Return true if the given column is hidden (i.e. not sensible).
+ */
+ static bool IsColumnHidden(nsTreeColumn* aColumn);
+
+ /**
+ * Scroll content into view.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void ScrollTo(PresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType);
+
+ /**
+ * Return true if the given node is table header element.
+ */
+ static bool IsHTMLTableHeader(nsIContent* aContent);
+
+ /**
+ * Returns true if the given string is empty or contains whitespace symbols
+ * only. In contrast to nsWhitespaceTokenizer class it takes into account
+ * non-breaking space (0xa0).
+ */
+ static bool IsWhitespaceString(const nsAString& aString);
+
+ /**
+ * Returns true if the given character is whitespace symbol.
+ */
+ static bool IsWhitespace(char16_t aChar) {
+ return aChar == ' ' || aChar == '\n' || aChar == '\r' || aChar == '\t' ||
+ aChar == 0xa0;
+ }
+
+ /*
+ * Return true if there are any observers of accessible events.
+ */
+ static bool AccEventObserversExist();
+
+ /**
+ * Notify accessible event observers of an event.
+ */
+ static void DispatchAccEvent(RefPtr<nsIAccessibleEvent> aEvent);
+
+ static bool IsDisplayContents(nsIContent* aContent);
+};
+
+#endif
diff --git a/accessible/base/nsEventShell.cpp b/accessible/base/nsEventShell.cpp
new file mode 100644
index 0000000000..d158102a79
--- /dev/null
+++ b/accessible/base/nsEventShell.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsEventShell.h"
+
+#include "nsAccUtils.h"
+#include "Logging.h"
+
+#include "mozilla/StaticPtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsEventShell
+////////////////////////////////////////////////////////////////////////////////
+
+void nsEventShell::FireEvent(AccEvent* aEvent) {
+ if (!aEvent || aEvent->mEventRule == AccEvent::eDoNotEmit) return;
+
+ Accessible* accessible = aEvent->GetAccessible();
+ NS_ENSURE_TRUE_VOID(accessible);
+
+ nsINode* node = accessible->GetNode();
+ if (node) {
+ sEventTargetNode = node;
+ sEventFromUserInput = aEvent->IsFromUserInput();
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEvents)) {
+ logging::MsgBegin("EVENTS", "events fired");
+ nsAutoString type;
+ GetAccService()->GetStringEventType(aEvent->GetEventType(), type);
+ logging::MsgEntry("type: %s", NS_ConvertUTF16toUTF8(type).get());
+ logging::AccessibleInfo("target", aEvent->GetAccessible());
+ logging::MsgEnd();
+ }
+#endif
+
+ accessible->HandleAccEvent(aEvent);
+ aEvent->mEventRule = AccEvent::eDoNotEmit;
+
+ sEventTargetNode = nullptr;
+}
+
+void nsEventShell::FireEvent(uint32_t aEventType, Accessible* aAccessible,
+ EIsFromUserInput aIsFromUserInput) {
+ NS_ENSURE_TRUE_VOID(aAccessible);
+
+ RefPtr<AccEvent> event =
+ new AccEvent(aEventType, aAccessible, aIsFromUserInput);
+
+ FireEvent(event);
+}
+
+void nsEventShell::GetEventAttributes(nsINode* aNode,
+ nsIPersistentProperties* aAttributes) {
+ if (aNode != sEventTargetNode) return;
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::eventFromInput,
+ sEventFromUserInput ? u"true"_ns : u"false"_ns);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsEventShell: private
+
+bool nsEventShell::sEventFromUserInput = false;
+StaticRefPtr<nsINode> nsEventShell::sEventTargetNode;
diff --git a/accessible/base/nsEventShell.h b/accessible/base/nsEventShell.h
new file mode 100644
index 0000000000..6ceb66a4f2
--- /dev/null
+++ b/accessible/base/nsEventShell.h
@@ -0,0 +1,66 @@
+/* -*- 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 _nsEventShell_H_
+#define _nsEventShell_H_
+
+#include "AccEvent.h"
+
+namespace mozilla {
+template <typename T>
+class StaticRefPtr;
+}
+class nsIPersistentProperties;
+
+/**
+ * Used for everything about events.
+ */
+class nsEventShell {
+ public:
+ /**
+ * Fire the accessible event.
+ */
+ static void FireEvent(mozilla::a11y::AccEvent* aEvent);
+
+ /**
+ * Fire accessible event of the given type for the given accessible.
+ *
+ * @param aEventType [in] the event type
+ * @param aAccessible [in] the event target
+ */
+ static void FireEvent(uint32_t aEventType,
+ mozilla::a11y::Accessible* aAccessible,
+ mozilla::a11y::EIsFromUserInput aIsFromUserInput =
+ mozilla::a11y::eAutoDetect);
+
+ /**
+ * Fire state change event.
+ */
+ static void FireEvent(mozilla::a11y::Accessible* aTarget, uint64_t aState,
+ bool aIsEnabled, bool aIsFromUserInput) {
+ RefPtr<mozilla::a11y::AccStateChangeEvent> stateChangeEvent =
+ new mozilla::a11y::AccStateChangeEvent(
+ aTarget, aState, aIsEnabled,
+ (aIsFromUserInput ? mozilla::a11y::eFromUserInput
+ : mozilla::a11y::eNoUserInput));
+ FireEvent(stateChangeEvent);
+ }
+
+ /**
+ * Append 'event-from-input' object attribute if the accessible event has
+ * been fired just now for the given node.
+ *
+ * @param aNode [in] the DOM node
+ * @param aAttributes [in, out] the attributes
+ */
+ static void GetEventAttributes(nsINode* aNode,
+ nsIPersistentProperties* aAttributes);
+
+ private:
+ static mozilla::StaticRefPtr<nsINode> sEventTargetNode;
+ static bool sEventFromUserInput;
+};
+
+#endif
diff --git a/accessible/base/nsTextEquivUtils.cpp b/accessible/base/nsTextEquivUtils.cpp
new file mode 100644
index 0000000000..77a3f77823
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.cpp
@@ -0,0 +1,355 @@
+/* -*- 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 "nsTextEquivUtils.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "nsCoreUtils.h"
+#include "mozilla/dom/Text.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+/**
+ * The accessible for which we are computing a text equivalent. It is useful
+ * for bailing out during recursive text computation, or for special cases
+ * like step f. of the ARIA implementation guide.
+ */
+static const Accessible* sInitiatorAcc = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsTextEquivUtils. Public.
+
+nsresult nsTextEquivUtils::GetNameFromSubtree(const Accessible* aAccessible,
+ nsAString& aName) {
+ aName.Truncate();
+
+ if (sInitiatorAcc) return NS_OK;
+
+ sInitiatorAcc = aAccessible;
+ if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
+ // XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsContent()) {
+ nsAutoString name;
+ AppendFromAccessibleChildren(aAccessible, &name);
+ name.CompressWhitespace();
+ if (!nsCoreUtils::IsWhitespaceString(name)) aName = name;
+ }
+ }
+
+ sInitiatorAcc = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsTextEquivUtils::GetTextEquivFromIDRefs(const Accessible* aAccessible,
+ nsAtom* aIDRefsAttr,
+ nsAString& aTextEquiv) {
+ aTextEquiv.Truncate();
+
+ nsIContent* content = aAccessible->GetContent();
+ if (!content) return NS_OK;
+
+ nsIContent* refContent = nullptr;
+ IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
+ while ((refContent = iter.NextElem())) {
+ if (!aTextEquiv.IsEmpty()) aTextEquiv += ' ';
+
+ nsresult rv =
+ AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsTextEquivUtils::AppendTextEquivFromContent(
+ const Accessible* aInitiatorAcc, nsIContent* aContent, nsAString* aString) {
+ // Prevent recursion which can cause infinite loops.
+ if (sInitiatorAcc) return NS_OK;
+
+ sInitiatorAcc = aInitiatorAcc;
+
+ // If the given content is not visible or isn't accessible then go down
+ // through the DOM subtree otherwise go down through accessible subtree and
+ // calculate the flat string.
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ bool isVisible = frame && frame->StyleVisibility()->IsVisible();
+
+ nsresult rv = NS_ERROR_FAILURE;
+ bool goThroughDOMSubtree = true;
+
+ if (isVisible) {
+ Accessible* accessible = sInitiatorAcc->Document()->GetAccessible(aContent);
+ if (accessible) {
+ rv = AppendFromAccessible(accessible, aString);
+ goThroughDOMSubtree = false;
+ }
+ }
+
+ if (goThroughDOMSubtree) rv = AppendFromDOMNode(aContent, aString);
+
+ sInitiatorAcc = nullptr;
+ return rv;
+}
+
+nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent,
+ nsAString* aString) {
+ if (aContent->IsText()) {
+ bool isHTMLBlock = false;
+
+ nsIContent* parentContent = aContent->GetFlattenedTreeParent();
+ if (parentContent) {
+ nsIFrame* frame = parentContent->GetPrimaryFrame();
+ if (frame) {
+ // If this text is inside a block level frame (as opposed to span
+ // level), we need to add spaces around that block's text, so we don't
+ // get words jammed together in final name.
+ const nsStyleDisplay* display = frame->StyleDisplay();
+ if (display->IsBlockOutsideStyle() ||
+ display->mDisplay == StyleDisplay::TableCell) {
+ isHTMLBlock = true;
+ if (!aString->IsEmpty()) {
+ aString->Append(char16_t(' '));
+ }
+ }
+ }
+ }
+
+ if (aContent->TextLength() > 0) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(
+ 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
+ nsIFrame::TrailingWhitespace::DontTrim);
+ aString->Append(text.mString);
+ } else {
+ // If aContent is an object that is display: none, we have no a frame.
+ aContent->GetAsText()->AppendTextTo(*aString);
+ }
+ if (isHTMLBlock && !aString->IsEmpty()) {
+ aString->Append(char16_t(' '));
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (aContent->IsHTMLElement() &&
+ aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
+ aString->AppendLiteral("\r\n");
+ return NS_OK;
+ }
+
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsTextEquivUtils. Private.
+
+nsresult nsTextEquivUtils::AppendFromAccessibleChildren(
+ const Accessible* aAccessible, nsAString* aString) {
+ nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
+
+ uint32_t childCount = aAccessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = aAccessible->GetChildAt(childIdx);
+ rv = AppendFromAccessible(child, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
+ nsAString* aString) {
+ // XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsContent()) {
+ nsresult rv =
+ AppendTextEquivFromTextContent(aAccessible->GetContent(), aString);
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv;
+ }
+
+ bool isEmptyTextEquiv = true;
+
+ // If the name is from tooltip then append it to result string in the end
+ // (see h. step of name computation guide).
+ nsAutoString text;
+ if (aAccessible->Name(text) != eNameFromTooltip)
+ isEmptyTextEquiv = !AppendString(aString, text);
+
+ // Implementation of f. step.
+ nsresult rv = AppendFromValue(aAccessible, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
+
+ // Implementation of g) step of text equivalent computation guide. Go down
+ // into subtree if accessible allows "text equivalent from subtree rule" or
+ // it's not root and not control.
+ if (isEmptyTextEquiv) {
+ if (ShouldIncludeInSubtreeCalculation(aAccessible)) {
+ rv = AppendFromAccessibleChildren(aAccessible, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
+ }
+ }
+
+ // Implementation of h. step
+ if (isEmptyTextEquiv && !text.IsEmpty()) {
+ AppendString(aString, text);
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
+ nsAString* aString) {
+ if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+
+ // Implementation of step f. of text equivalent computation. If the given
+ // accessible is not root accessible (the accessible the text equivalent is
+ // computed for in the end) then append accessible value. Otherwise append
+ // value if and only if the given accessible is in the middle of its parent.
+
+ nsAutoString text;
+ if (aAccessible != sInitiatorAcc) {
+ aAccessible->Value(text);
+
+ return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
+ }
+
+ // XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsDoc()) return NS_ERROR_UNEXPECTED;
+
+ nsIContent* content = aAccessible->GetContent();
+
+ for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
+ childContent = childContent->GetPreviousSibling()) {
+ // check for preceding text...
+ if (!childContent->TextIsOnlyWhitespace()) {
+ for (nsIContent* siblingContent = content->GetNextSibling();
+ siblingContent; siblingContent = siblingContent->GetNextSibling()) {
+ // .. and subsequent text
+ if (!siblingContent->TextIsOnlyWhitespace()) {
+ aAccessible->Value(text);
+
+ return AppendString(aString, text) ? NS_OK
+ : NS_OK_NO_NAME_CLAUSE_HANDLED;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+}
+
+nsresult nsTextEquivUtils::AppendFromDOMChildren(nsIContent* aContent,
+ nsAString* aString) {
+ for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ nsresult rv = AppendFromDOMNode(childContent, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsTextEquivUtils::AppendFromDOMNode(nsIContent* aContent,
+ nsAString* aString) {
+ nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return NS_OK;
+
+ if (aContent->IsXULElement()) {
+ nsAutoString textEquivalent;
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::label, kNameSpaceID_XUL)) {
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ textEquivalent);
+ } else {
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label,
+ textEquivalent);
+ }
+
+ if (textEquivalent.IsEmpty()) {
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext,
+ textEquivalent);
+ }
+
+ AppendString(aString, textEquivalent);
+ }
+
+ return AppendFromDOMChildren(aContent, aString);
+}
+
+bool nsTextEquivUtils::AppendString(nsAString* aString,
+ const nsAString& aTextEquivalent) {
+ if (aTextEquivalent.IsEmpty()) return false;
+
+ // Insert spaces to insure that words from controls aren't jammed together.
+ if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
+ aString->Append(char16_t(' '));
+
+ aString->Append(aTextEquivalent);
+
+ if (!nsCoreUtils::IsWhitespace(aString->Last()))
+ aString->Append(char16_t(' '));
+
+ return true;
+}
+
+uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
+#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ case roles::geckoRole: \
+ return nameRule;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_CRASH("Unknown role.");
+ }
+
+#undef ROLE
+}
+
+bool nsTextEquivUtils::ShouldIncludeInSubtreeCalculation(
+ Accessible* aAccessible) {
+ uint32_t nameRule = GetRoleRule(aAccessible->Role());
+ if (nameRule == eNameFromSubtreeRule) {
+ return true;
+ }
+ if (!(nameRule & eNameFromSubtreeIfReqRule)) {
+ return false;
+ }
+
+ if (aAccessible == sInitiatorAcc) {
+ // We're calculating the text equivalent for this accessible, but this
+ // accessible should only be included when calculating the text equivalent
+ // for something else.
+ return false;
+ }
+
+ // sInitiatorAcc can be null when, for example, Accessible::Value calls
+ // GetTextEquivFromSubtree.
+ role initiatorRole = sInitiatorAcc ? sInitiatorAcc->Role() : roles::NOTHING;
+ if (initiatorRole == roles::OUTLINEITEM &&
+ aAccessible->Role() == roles::GROUPING) {
+ // Child treeitems are contained in a group. We don't want to include those
+ // in the parent treeitem's text equivalent.
+ return false;
+ }
+
+ return true;
+}
diff --git a/accessible/base/nsTextEquivUtils.h b/accessible/base/nsTextEquivUtils.h
new file mode 100644
index 0000000000..41fada6b10
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.h
@@ -0,0 +1,164 @@
+/* -*- 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 _nsTextEquivUtils_H_
+#define _nsTextEquivUtils_H_
+
+#include "Accessible.h"
+#include "Role.h"
+
+class nsIContent;
+
+/**
+ * Text equivalent computation rules (see nsTextEquivUtils::gRoleToNameRulesMap)
+ */
+enum ETextEquivRule {
+ // No rule.
+ eNoNameRule = 0x00,
+
+ // Walk into subtree only if the currently navigated accessible is not root
+ // accessible (i.e. if the accessible is part of text equivalent computation).
+ eNameFromSubtreeIfReqRule = 0x01,
+
+ // Text equivalent computation from subtree is allowed.
+ eNameFromSubtreeRule = 0x03,
+
+ // The accessible allows to append its value to text equivalent.
+ // XXX: This is temporary solution. Once we move accessible value of links
+ // and linkable accessibles to MSAA part we can remove this.
+ eNameFromValueRule = 0x04
+};
+
+/**
+ * The class provides utils methods to compute the accessible name and
+ * description.
+ */
+class nsTextEquivUtils {
+ public:
+ typedef mozilla::a11y::Accessible Accessible;
+
+ /**
+ * Determines if the accessible has a given name rule.
+ *
+ * @param aAccessible [in] the given accessible
+ * @param aRule [in] a given name rule
+ * @return true if the accessible has the rule
+ */
+ static inline bool HasNameRule(Accessible* aAccessible,
+ ETextEquivRule aRule) {
+ return (GetRoleRule(aAccessible->Role()) & aRule) == aRule;
+ }
+
+ /**
+ * Calculates the name from accessible subtree if allowed.
+ *
+ * @param aAccessible [in] the given accessible
+ * @param aName [out] accessible name
+ */
+ static nsresult GetNameFromSubtree(const Accessible* aAccessible,
+ nsAString& aName);
+
+ /**
+ * Calculates text equivalent from the subtree. Similar to GetNameFromSubtree.
+ * However it returns not empty result for things like HTML p.
+ */
+ static void GetTextEquivFromSubtree(const Accessible* aAccessible,
+ nsString& aTextEquiv) {
+ aTextEquiv.Truncate();
+
+ AppendFromAccessibleChildren(aAccessible, &aTextEquiv);
+ aTextEquiv.CompressWhitespace();
+ }
+
+ /**
+ * Calculates text equivalent for the given accessible from its IDRefs
+ * attribute (like aria-labelledby or aria-describedby).
+ *
+ * @param aAccessible [in] the accessible text equivalent is computed for
+ * @param aIDRefsAttr [in] IDRefs attribute on DOM node of the accessible
+ * @param aTextEquiv [out] result text equivalent
+ */
+ static nsresult GetTextEquivFromIDRefs(const Accessible* aAccessible,
+ nsAtom* aIDRefsAttr,
+ nsAString& aTextEquiv);
+
+ /**
+ * Calculates the text equivalent from the given content and its subtree if
+ * allowed and appends it to the given string.
+ *
+ * @param aInitiatorAcc [in] the accessible text equivalent is computed for
+ * in the end (root accessible of text equivalent
+ * calculation recursion)
+ * @param aContent [in] the given content the text equivalent is
+ * computed from
+ * @param aString [in, out] the string
+ */
+ static nsresult AppendTextEquivFromContent(const Accessible* aInitiatorAcc,
+ nsIContent* aContent,
+ nsAString* aString);
+
+ /**
+ * Calculates the text equivalent from the given text content (may be text
+ * node or html:br) and appends it to the given string.
+ *
+ * @param aContent [in] the text content
+ * @param aString [in, out] the string
+ */
+ static nsresult AppendTextEquivFromTextContent(nsIContent* aContent,
+ nsAString* aString);
+
+ private:
+ /**
+ * Iterates accessible children and calculates text equivalent from each
+ * child.
+ */
+ static nsresult AppendFromAccessibleChildren(const Accessible* aAccessible,
+ nsAString* aString);
+
+ /**
+ * Calculates text equivalent from the given accessible and its subtree if
+ * allowed.
+ */
+ static nsresult AppendFromAccessible(Accessible* aAccessible,
+ nsAString* aString);
+
+ /**
+ * Calculates text equivalent from the value of given accessible.
+ */
+ static nsresult AppendFromValue(Accessible* aAccessible, nsAString* aString);
+ /**
+ * Iterates DOM children and calculates text equivalent from each child node.
+ */
+ static nsresult AppendFromDOMChildren(nsIContent* aContent,
+ nsAString* aString);
+
+ /**
+ * Calculates text equivalent from the given DOM node and its subtree if
+ * allowed.
+ */
+ static nsresult AppendFromDOMNode(nsIContent* aContent, nsAString* aString);
+
+ /**
+ * Concatenates strings and appends space between them. Returns true if
+ * text equivalent string was appended.
+ */
+ static bool AppendString(nsAString* aString,
+ const nsAString& aTextEquivalent);
+
+ /**
+ * Returns the rule (constant of ETextEquivRule) for a given role.
+ */
+ static uint32_t GetRoleRule(mozilla::a11y::roles::Role aRole);
+
+ /**
+ * Returns true if a given accessible should be included when calculating
+ * the text equivalent for the initiator's subtree.
+ */
+ static bool ShouldIncludeInSubtreeCalculation(Accessible* aAccessible);
+};
+
+#endif