summaryrefslogtreecommitdiffstats
path: root/accessible/base
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /accessible/base
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'accessible/base')
-rw-r--r--accessible/base/ARIAMap.cpp1008
-rw-r--r--accessible/base/ARIAMap.h305
-rw-r--r--accessible/base/ARIAStateMap.cpp376
-rw-r--r--accessible/base/ARIAStateMap.h67
-rw-r--r--accessible/base/AccEvent.cpp271
-rw-r--r--accessible/base/AccEvent.h570
-rw-r--r--accessible/base/AccGroupInfo.cpp227
-rw-r--r--accessible/base/AccGroupInfo.h114
-rw-r--r--accessible/base/AccIterator.cpp414
-rw-r--r--accessible/base/AccIterator.h325
-rw-r--r--accessible/base/AccTypes.h94
-rw-r--r--accessible/base/AccessibleOrProxy.cpp27
-rw-r--r--accessible/base/AccessibleOrProxy.h123
-rw-r--r--accessible/base/Asserts.cpp26
-rw-r--r--accessible/base/DocManager.cpp594
-rw-r--r--accessible/base/DocManager.h189
-rw-r--r--accessible/base/EmbeddedObjCollector.cpp81
-rw-r--r--accessible/base/EmbeddedObjCollector.h69
-rw-r--r--accessible/base/EventQueue.cpp344
-rw-r--r--accessible/base/EventQueue.h77
-rw-r--r--accessible/base/EventTree.cpp618
-rw-r--r--accessible/base/EventTree.h121
-rw-r--r--accessible/base/Filters.cpp51
-rw-r--r--accessible/base/Filters.h50
-rw-r--r--accessible/base/FocusManager.cpp404
-rw-r--r--accessible/base/FocusManager.h132
-rw-r--r--accessible/base/Logging.cpp1039
-rw-r--r--accessible/base/Logging.h225
-rw-r--r--accessible/base/MarkupMap.h340
-rw-r--r--accessible/base/NotificationController.cpp947
-rw-r--r--accessible/base/NotificationController.h439
-rw-r--r--accessible/base/Platform.h88
-rw-r--r--accessible/base/Relation.h108
-rw-r--r--accessible/base/RelationType.h163
-rw-r--r--accessible/base/RelationTypeMap.h154
-rw-r--r--accessible/base/Role.h995
-rw-r--r--accessible/base/RoleMap.h1370
-rw-r--r--accessible/base/SelectionManager.cpp232
-rw-r--r--accessible/base/SelectionManager.h133
-rw-r--r--accessible/base/States.h285
-rw-r--r--accessible/base/Statistics.h39
-rw-r--r--accessible/base/StyleInfo.cpp122
-rw-r--r--accessible/base/StyleInfo.h48
-rw-r--r--accessible/base/TextAttrs.cpp913
-rw-r--r--accessible/base/TextAttrs.h478
-rw-r--r--accessible/base/TextRange-inl.h29
-rw-r--r--accessible/base/TextRange.cpp380
-rw-r--r--accessible/base/TextRange.h270
-rw-r--r--accessible/base/TextUpdater.cpp202
-rw-r--r--accessible/base/TextUpdater.h96
-rw-r--r--accessible/base/TreeWalker.cpp325
-rw-r--r--accessible/base/TreeWalker.h144
-rw-r--r--accessible/base/moz.build114
-rw-r--r--accessible/base/nsAccCache.h28
-rw-r--r--accessible/base/nsAccUtils.cpp447
-rw-r--r--accessible/base/nsAccUtils.h243
-rw-r--r--accessible/base/nsAccessibilityService.cpp1885
-rw-r--r--accessible/base/nsAccessibilityService.h442
-rw-r--r--accessible/base/nsAccessiblePivot.cpp924
-rw-r--r--accessible/base/nsAccessiblePivot.h144
-rw-r--r--accessible/base/nsCoreUtils.cpp683
-rw-r--r--accessible/base/nsCoreUtils.h329
-rw-r--r--accessible/base/nsEventShell.cpp79
-rw-r--r--accessible/base/nsEventShell.h67
-rw-r--r--accessible/base/nsTextEquivUtils.cpp364
-rw-r--r--accessible/base/nsTextEquivUtils.h162
66 files changed, 22152 insertions, 0 deletions
diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp
new file mode 100644
index 000000000..c29d37873
--- /dev/null
+++ b/accessible/base/ARIAMap.cpp
@@ -0,0 +1,1008 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ARIAMap.h"
+
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsAttrName.h"
+#include "nsWhitespaceTokenizer.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::aria;
+
+static const uint32_t kGenericAccType = 0;
+
+/**
+ * This list of WAI-defined roles are currently hardcoded.
+ * Eventually we will most likely be loading an RDF resource that contains this information
+ * Using RDF will also allow for role extensibility. See bug 280138.
+ *
+ * Definition of nsRoleMapEntry contains comments explaining this table.
+ *
+ * When no Role enum mapping exists for an ARIA role, the role will be exposed
+ * via the object attribute "xml-roles".
+ */
+
+static const nsRoleMapEntry sWAIRoleMaps[] =
+{
+ { // alert
+ &nsGkAtoms::alert,
+ roles::ALERT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eAlert,
+ kNoReqStates
+ },
+ { // alertdialog
+ &nsGkAtoms::alertdialog,
+ roles::DIALOG,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // application
+ &nsGkAtoms::application,
+ roles::APPLICATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // article
+ &nsGkAtoms::article,
+ roles::DOCUMENT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eReadonlyUntilEditable
+ },
+ { // banner
+ &nsGkAtoms::banner,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // button
+ &nsGkAtoms::button,
+ roles::PUSHBUTTON,
+ kUseMapRole,
+ eNoValue,
+ ePressAction,
+ eNoLiveAttr,
+ eButton,
+ kNoReqStates
+ // eARIAPressed is auto applied on any button
+ },
+ { // cell
+ &nsGkAtoms::cell,
+ roles::CELL,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates
+ },
+ { // checkbox
+ &nsGkAtoms::checkbox,
+ roles::CHECKBUTTON,
+ kUseMapRole,
+ eNoValue,
+ eCheckUncheckAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableMixed,
+ eARIAReadonly
+ },
+ { // columnheader
+ &nsGkAtoms::columnheader,
+ roles::COLUMNHEADER,
+ kUseMapRole,
+ eNoValue,
+ eSortAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectableIfDefined,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // combobox
+ &nsGkAtoms::combobox,
+ roles::COMBOBOX,
+ kUseMapRole,
+ eNoValue,
+ eOpenCloseAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::COLLAPSED | states::HASPOPUP | states::VERTICAL,
+ eARIAAutoComplete,
+ eARIAReadonly,
+ eARIAOrientation
+ },
+ { // complementary
+ &nsGkAtoms::complementary,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // contentinfo
+ &nsGkAtoms::contentinfo,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // dialog
+ &nsGkAtoms::dialog,
+ roles::DIALOG,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // directory
+ &nsGkAtoms::directory,
+ roles::LIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eList,
+ kNoReqStates
+ },
+ { // document
+ &nsGkAtoms::document,
+ roles::DOCUMENT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eReadonlyUntilEditable
+ },
+ { // feed
+ &nsGkAtoms::feed,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // form
+ &nsGkAtoms::form,
+ roles::FORM,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // grid
+ &nsGkAtoms::grid,
+ roles::TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect | eTable,
+ kNoReqStates,
+ eARIAMultiSelectable,
+ eARIAReadonlyOrEditable,
+ eFocusableUntilDisabled
+ },
+ { // gridcell
+ &nsGkAtoms::gridcell,
+ roles::GRID_CELL,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectable,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // group
+ &nsGkAtoms::group,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // heading
+ &nsGkAtoms::heading,
+ roles::HEADING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // img
+ &nsGkAtoms::img,
+ roles::GRAPHIC,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // key
+ &nsGkAtoms::key,
+ roles::KEY,
+ kUseMapRole,
+ eNoValue,
+ ePressAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAPressed
+ },
+ { // link
+ &nsGkAtoms::link,
+ roles::LINK,
+ kUseMapRole,
+ eNoValue,
+ eJumpAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::LINKED
+ },
+ { // list
+ &nsGkAtoms::list,
+ roles::LIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eList,
+ states::READONLY
+ },
+ { // listbox
+ &nsGkAtoms::listbox,
+ roles::LISTBOX,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eListControl | eSelect,
+ states::VERTICAL,
+ eARIAMultiSelectable,
+ eARIAReadonly,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // listitem
+ &nsGkAtoms::listitem,
+ roles::LISTITEM,
+ kUseMapRole,
+ eNoValue,
+ eNoAction, // XXX: should depend on state, parent accessible
+ eNoLiveAttr,
+ kGenericAccType,
+ states::READONLY
+ },
+ { // log
+ &nsGkAtoms::log_,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ ePoliteLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // main
+ &nsGkAtoms::main,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // marquee
+ &nsGkAtoms::marquee,
+ roles::ANIMATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eOffLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // math
+ &nsGkAtoms::math,
+ roles::FLAT_EQUATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // menu
+ &nsGkAtoms::menu,
+ roles::MENUPOPUP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction, // XXX: technically accessibles of menupopup role haven't
+ // any action, but menu can be open or close.
+ eNoLiveAttr,
+ kGenericAccType,
+ states::VERTICAL,
+ eARIAOrientation
+ },
+ { // menubar
+ &nsGkAtoms::menubar,
+ roles::MENUBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // menuitem
+ &nsGkAtoms::menuitem,
+ roles::MENUITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckedMixed
+ },
+ { // menuitemcheckbox
+ &nsGkAtoms::menuitemcheckbox,
+ roles::CHECK_MENU_ITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableMixed
+ },
+ { // menuitemradio
+ &nsGkAtoms::menuitemradio,
+ roles::RADIO_MENU_ITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // navigation
+ &nsGkAtoms::navigation,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // none
+ &nsGkAtoms::none,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // note
+ &nsGkAtoms::note_,
+ roles::NOTE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // option
+ &nsGkAtoms::option,
+ roles::OPTION,
+ kUseMapRole,
+ eNoValue,
+ eSelectAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable,
+ eARIACheckedMixed
+ },
+ { // presentation
+ &nsGkAtoms::presentation,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // progressbar
+ &nsGkAtoms::progressbar,
+ roles::PROGRESSBAR,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::READONLY,
+ eIndeterminateIfNoValue
+ },
+ { // radio
+ &nsGkAtoms::radio,
+ roles::RADIOBUTTON,
+ kUseMapRole,
+ eNoValue,
+ eSelectAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // radiogroup
+ &nsGkAtoms::radiogroup,
+ roles::RADIO_GROUP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAOrientation
+ },
+ { // region
+ &nsGkAtoms::region,
+ roles::PANE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // row
+ &nsGkAtoms::row,
+ roles::ROW,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableRow,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // rowgroup
+ &nsGkAtoms::rowgroup,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // rowheader
+ &nsGkAtoms::rowheader,
+ roles::ROWHEADER,
+ kUseMapRole,
+ eNoValue,
+ eSortAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectableIfDefined,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // scrollbar
+ &nsGkAtoms::scrollbar,
+ roles::SCROLLBAR,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::VERTICAL,
+ eARIAOrientation,
+ eARIAReadonly
+ },
+ { // search
+ &nsGkAtoms::search,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // searchbox
+ &nsGkAtoms::searchbox,
+ roles::ENTRY,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAAutoComplete,
+ eARIAMultiline,
+ eARIAReadonlyOrEditable
+ },
+ { // separator
+ &nsGkAtoms::separator_,
+ roles::SEPARATOR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // slider
+ &nsGkAtoms::slider,
+ roles::SLIDER,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation,
+ eARIAReadonly
+ },
+ { // spinbutton
+ &nsGkAtoms::spinbutton,
+ roles::SPINBUTTON,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAReadonly
+ },
+ { // status
+ &nsGkAtoms::status,
+ roles::STATUSBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ ePoliteLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // switch
+ &nsGkAtoms::_switch,
+ roles::SWITCH,
+ kUseMapRole,
+ eNoValue,
+ eCheckUncheckAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // tab
+ &nsGkAtoms::tab,
+ roles::PAGETAB,
+ kUseMapRole,
+ eNoValue,
+ eSwitchAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // table
+ &nsGkAtoms::table,
+ roles::TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTable,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // tablist
+ &nsGkAtoms::tablist,
+ roles::PAGETABLIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // tabpanel
+ &nsGkAtoms::tabpanel,
+ roles::PROPERTYPAGE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // textbox
+ &nsGkAtoms::textbox,
+ roles::ENTRY,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAAutoComplete,
+ eARIAMultiline,
+ eARIAReadonlyOrEditable
+ },
+ { // timer
+ &nsGkAtoms::timer,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eOffLiveAttr,
+ kNoReqStates
+ },
+ { // toolbar
+ &nsGkAtoms::toolbar,
+ roles::TOOLBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // tooltip
+ &nsGkAtoms::tooltip,
+ roles::TOOLTIP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // tree
+ &nsGkAtoms::tree,
+ roles::OUTLINE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect,
+ states::VERTICAL,
+ eARIAReadonly,
+ eARIAMultiSelectable,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // treegrid
+ &nsGkAtoms::treegrid,
+ roles::TREE_TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect | eTable,
+ states::VERTICAL,
+ eARIAReadonlyOrEditable,
+ eARIAMultiSelectable,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // treeitem
+ &nsGkAtoms::treeitem,
+ roles::OUTLINEITEM,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction, // XXX: should expose second 'expand/collapse' action based
+ // on states
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable,
+ eARIACheckedMixed
+ }
+};
+
+static const nsRoleMapEntry sLandmarkRoleMap = {
+ &nsGkAtoms::_empty,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+};
+
+nsRoleMapEntry aria::gEmptyRoleMap = {
+ &nsGkAtoms::_empty,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+};
+
+/**
+ * Universal (Global) states:
+ * The following state rules are applied to any accessible element,
+ * whether there is an ARIA role or not:
+ */
+static const EStateRule sWAIUnivStateMap[] = {
+ eARIABusy,
+ eARIADisabled,
+ eARIAExpanded, // Currently under spec review but precedent exists
+ eARIAHasPopup, // Note this is technically a "property"
+ eARIAInvalid,
+ eARIAModal,
+ eARIARequired, // XXX not global, Bug 553117
+ eARIANone
+};
+
+
+/**
+ * ARIA attribute map for attribute characteristics.
+ * @note ARIA attributes that don't have any flags are not included here.
+ */
+
+struct AttrCharacteristics
+{
+ nsIAtom** attributeName;
+ const uint8_t characteristics;
+};
+
+static const AttrCharacteristics gWAIUnivAttrMap[] = {
+ {&nsGkAtoms::aria_activedescendant, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_atomic, ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_busy, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_checked, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, /* exposes checkable obj attr */
+ {&nsGkAtoms::aria_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_details, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_errormessage, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_grabbed, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_hidden, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, /* handled special way */
+ {&nsGkAtoms::aria_invalid, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_label, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_labelledby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_level, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_live, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_modal, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_multiline, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_multiselectable, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_owns, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_orientation, ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_posinset, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_pressed, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_readonly, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_relevant, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_sort, ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ }
+};
+
+namespace {
+
+struct RoleComparator
+{
+ const nsDependentSubstring& mRole;
+ explicit RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {}
+ int operator()(const nsRoleMapEntry& aEntry) const {
+ return Compare(mRole, aEntry.ARIARoleString());
+ }
+};
+
+}
+
+const nsRoleMapEntry*
+aria::GetRoleMap(dom::Element* aEl)
+{
+ return GetRoleMapFromIndex(GetRoleMapIndex(aEl));
+}
+
+uint8_t
+aria::GetRoleMapIndex(dom::Element* aEl)
+{
+ nsAutoString roles;
+ if (!aEl || !aEl->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roles) ||
+ roles.IsEmpty()) {
+ // We treat role="" as if the role attribute is absent (per aria spec:8.1.1)
+ return NO_ROLE_MAP_ENTRY_INDEX;
+ }
+
+ nsWhitespaceTokenizer tokenizer(roles);
+ while (tokenizer.hasMoreTokens()) {
+ // Do a binary search through table for the next role in role list
+ const nsDependentSubstring role = tokenizer.nextToken();
+ size_t idx;
+ if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps),
+ RoleComparator(role), &idx)) {
+ return idx;
+ }
+ }
+
+ // Always use some entry index if there is a non-empty role string
+ // To ensure an accessible object is created
+ return LANDMARK_ROLE_MAP_ENTRY_INDEX;
+}
+
+
+const nsRoleMapEntry*
+aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex)
+{
+ switch (aRoleMapIndex) {
+ case NO_ROLE_MAP_ENTRY_INDEX:
+ return nullptr;
+ case EMPTY_ROLE_MAP_ENTRY_INDEX:
+ return &gEmptyRoleMap;
+ case LANDMARK_ROLE_MAP_ENTRY_INDEX:
+ return &sLandmarkRoleMap;
+ default:
+ return sWAIRoleMaps + aRoleMapIndex;
+ }
+}
+
+uint8_t
+aria::GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMapEntry)
+{
+ if (aRoleMapEntry == nullptr) {
+ return NO_ROLE_MAP_ENTRY_INDEX;
+ } else if (aRoleMapEntry == &gEmptyRoleMap) {
+ return EMPTY_ROLE_MAP_ENTRY_INDEX;
+ } else if (aRoleMapEntry == &sLandmarkRoleMap) {
+ return LANDMARK_ROLE_MAP_ENTRY_INDEX;
+ } else {
+ return aRoleMapEntry - sWAIRoleMaps;
+ }
+}
+
+uint64_t
+aria::UniversalStatesFor(mozilla::dom::Element* aElement)
+{
+ uint64_t state = 0;
+ uint32_t index = 0;
+ while (MapToState(sWAIUnivStateMap[index], aElement, &state))
+ index++;
+
+ return state;
+}
+
+uint8_t
+aria::AttrCharacteristicsFor(nsIAtom* aAtom)
+{
+ for (uint32_t i = 0; i < ArrayLength(gWAIUnivAttrMap); i++)
+ if (*gWAIUnivAttrMap[i].attributeName == aAtom)
+ return gWAIUnivAttrMap[i].characteristics;
+
+ return 0;
+}
+
+bool
+aria::HasDefinedARIAHidden(nsIContent* aContent)
+{
+ return aContent &&
+ nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_hidden) &&
+ !aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_hidden,
+ nsGkAtoms::_false, eCaseMatters);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AttrIterator class
+
+bool
+AttrIterator::Next(nsAString& aAttrName, nsAString& aAttrValue)
+{
+ while (mAttrIdx < mAttrCount) {
+ const nsAttrName* attr = mContent->GetAttrNameAt(mAttrIdx);
+ mAttrIdx++;
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsIAtom* attrAtom = attr->Atom();
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
+ continue; // Not ARIA
+
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
+ if (attrFlags & ATTR_BYPASSOBJ)
+ continue; // No need to handle exposing as obj attribute here
+
+ if ((attrFlags & ATTR_VALTOKEN) &&
+ !nsAccUtils::HasDefinedARIAToken(mContent, attrAtom))
+ continue; // only expose token based attributes if they are defined
+
+ if ((attrFlags & ATTR_BYPASSOBJ_IF_FALSE) &&
+ mContent->AttrValueIs(kNameSpaceID_None, attrAtom,
+ nsGkAtoms::_false, eCaseMatters)) {
+ continue; // only expose token based attribute if value is not 'false'.
+ }
+
+ nsAutoString value;
+ if (mContent->GetAttr(kNameSpaceID_None, attrAtom, value)) {
+ aAttrName.Assign(Substring(attrStr, 5));
+ aAttrValue.Assign(value);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h
new file mode 100644
index 000000000..4d603c04b
--- /dev/null
+++ b/accessible/base/ARIAMap.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_aria_ARIAMap_h_
+#define mozilla_a11y_aria_ARIAMap_h_
+
+#include "ARIAStateMap.h"
+#include "mozilla/a11y/AccTypes.h"
+#include "mozilla/a11y/Role.h"
+
+#include "nsIAtom.h"
+#include "nsIContent.h"
+
+class nsINode;
+
+////////////////////////////////////////////////////////////////////////////////
+// Value constants
+
+/**
+ * Used to define if role requires to expose Value interface.
+ */
+enum EValueRule
+{
+ /**
+ * Value interface isn't exposed.
+ */
+ eNoValue,
+
+ /**
+ * Value interface is implemented, supports value, min and max from
+ * aria-valuenow, aria-valuemin and aria-valuemax.
+ */
+ eHasValueMinMax
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Action constants
+
+/**
+ * Used to define if the role requires to expose action.
+ */
+enum EActionRule
+{
+ eNoAction,
+ eActivateAction,
+ eClickAction,
+ ePressAction,
+ eCheckUncheckAction,
+ eExpandAction,
+ eJumpAction,
+ eOpenCloseAction,
+ eSelectAction,
+ eSortAction,
+ eSwitchAction
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Live region constants
+
+/**
+ * Used to define if role exposes default value of aria-live attribute.
+ */
+enum ELiveAttrRule
+{
+ eNoLiveAttr,
+ eOffLiveAttr,
+ ePoliteLiveAttr
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Role constants
+
+/**
+ * ARIA role overrides role from native markup.
+ */
+const bool kUseMapRole = true;
+
+/**
+ * ARIA role doesn't override the role from native markup.
+ */
+const bool kUseNativeRole = false;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIA attribute characteristic masks
+
+/**
+ * This mask indicates the attribute should not be exposed as an object
+ * attribute via the catch-all logic in Accessible::Attributes().
+ * This means it either isn't mean't to be exposed as an object attribute, or
+ * that it should, but is already handled in other code.
+ */
+const uint8_t ATTR_BYPASSOBJ = 0x1 << 0;
+const uint8_t ATTR_BYPASSOBJ_IF_FALSE = 0x1 << 1;
+
+/**
+ * This mask indicates the attribute is expected to have an NMTOKEN or bool value.
+ * (See for example usage in Accessible::Attributes())
+ */
+const uint8_t ATTR_VALTOKEN = 0x1 << 2;
+
+/**
+ * Indicate the attribute is global state or property (refer to
+ * http://www.w3.org/TR/wai-aria/states_and_properties#global_states).
+ */
+const uint8_t ATTR_GLOBAL = 0x1 << 3;
+
+////////////////////////////////////////////////////////////////////////////////
+// State map entry
+
+/**
+ * Used in nsRoleMapEntry.state if no nsIAccessibleStates are automatic for
+ * a given role.
+ */
+#define kNoReqStates 0
+
+////////////////////////////////////////////////////////////////////////////////
+// Role map entry
+
+/**
+ * For each ARIA role, this maps the nsIAccessible information.
+ */
+struct nsRoleMapEntry
+{
+ /**
+ * Return true if matches to the given ARIA role.
+ */
+ bool Is(nsIAtom* aARIARole) const
+ { return *roleAtom == aARIARole; }
+
+ /**
+ * Return true if ARIA role has the given accessible type.
+ */
+ bool IsOfType(mozilla::a11y::AccGenericType aType) const
+ { return accTypes & aType; }
+
+ /**
+ * Return ARIA role.
+ */
+ const nsDependentAtomString ARIARoleString() const
+ { return nsDependentAtomString(*roleAtom); }
+
+ // ARIA role: string representation such as "button"
+ nsIAtom** roleAtom;
+
+ // Role mapping rule: maps to enum Role
+ mozilla::a11y::role role;
+
+ // Role rule: whether to use mapped role or native semantics
+ bool roleRule;
+
+ // Value mapping rule: how to compute accessible value
+ EValueRule valueRule;
+
+ // Action mapping rule, how to expose accessible action
+ EActionRule actionRule;
+
+ // 'live' and 'container-live' object attributes mapping rule: how to expose
+ // these object attributes if ARIA 'live' attribute is missed.
+ ELiveAttrRule liveAttRule;
+
+ // Accessible types this role belongs to.
+ uint32_t accTypes;
+
+ // Automatic state mapping rule: always include in states
+ uint64_t state; // or kNoReqStates if no default state for this role
+
+ // ARIA properties supported for this role (in other words, the aria-foo
+ // attribute to accessible states mapping rules).
+ // Currently you cannot have unlimited mappings, because
+ // a variable sized array would not allow the use of
+ // C++'s struct initialization feature.
+ mozilla::a11y::aria::EStateRule attributeMap1;
+ mozilla::a11y::aria::EStateRule attributeMap2;
+ mozilla::a11y::aria::EStateRule attributeMap3;
+ mozilla::a11y::aria::EStateRule attributeMap4;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIA map
+
+/**
+ * These provide the mappings for WAI-ARIA roles, states and properties using
+ * the structs defined in this file and ARIAStateMap files.
+ */
+namespace mozilla {
+namespace a11y {
+namespace aria {
+
+/**
+ * Empty role map entry. Used by accessibility service to create an accessible
+ * if the accessible can't use role of used accessible class. For example,
+ * it is used for table cells that aren't contained by table.
+ */
+extern nsRoleMapEntry gEmptyRoleMap;
+
+/**
+ * Constants for the role map entry index to indicate that the role map entry
+ * isn't in sWAIRoleMaps, but rather is a special entry: nullptr,
+ * gEmptyRoleMap, and sLandmarkRoleMap
+ */
+const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2;
+const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1;
+const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX;
+
+/**
+ * Get the role map entry for a given DOM node. This will use the first
+ * ARIA role if the role attribute provides a space delimited list of roles.
+ *
+ * @param aEl [in] the DOM node to get the role map entry for
+ * @return a pointer to the role map entry for the ARIA role, or nullptr
+ * if none
+ */
+const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
+
+/**
+ * Get the role map entry pointer's index for a given DOM node. This will use
+ * the first ARIA role if the role attribute provides a space delimited list of
+ * roles.
+ *
+ * @param aEl [in] the DOM node to get the role map entry for
+ * @return the index of the pointer to the role map entry for the ARIA
+ * role, or NO_ROLE_MAP_ENTRY_INDEX if none
+ */
+uint8_t GetRoleMapIndex(dom::Element* aEl);
+
+/**
+ * Get the role map entry pointer for a given role map entry index.
+ *
+ * @param aRoleMapIndex [in] the role map index to get the role map entry
+ * pointer for
+ * @return a pointer to the role map entry for the ARIA role,
+ * or nullptr, if none
+ */
+const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex);
+
+/**
+ * Get the role map entry index for a given role map entry pointer. If the role
+ * map entry is within sWAIRoleMaps, return the index within that array,
+ * otherwise return one of the special index constants listed above.
+ *
+ * @param aRoleMap [in] the role map entry pointer to get the index for
+ * @return the index of the pointer to the role map entry, or
+ * NO_ROLE_MAP_ENTRY_INDEX if none
+ */
+uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap);
+
+/**
+ * Return accessible state from ARIA universal states applied to the given
+ * element.
+ */
+uint64_t UniversalStatesFor(mozilla::dom::Element* aElement);
+
+/**
+ * Get the ARIA attribute characteristics for a given ARIA attribute.
+ *
+ * @param aAtom ARIA attribute
+ * @return A bitflag representing the attribute characteristics
+ * (see above for possible bit masks, prefixed "ATTR_")
+ */
+uint8_t AttrCharacteristicsFor(nsIAtom* aAtom);
+
+/**
+ * Return true if the element has defined aria-hidden.
+ */
+bool HasDefinedARIAHidden(nsIContent* aContent);
+
+ /**
+ * Represents a simple enumerator for iterating through ARIA attributes
+ * exposed as object attributes on a given accessible.
+ */
+class AttrIterator
+{
+public:
+ explicit AttrIterator(nsIContent* aContent) :
+ mContent(aContent), mAttrIdx(0)
+ {
+ mAttrCount = mContent->GetAttrCount();
+ }
+
+ bool Next(nsAString& aAttrName, nsAString& aAttrValue);
+
+private:
+ AttrIterator() = delete;
+ AttrIterator(const AttrIterator&) = delete;
+ AttrIterator& operator= (const AttrIterator&) = delete;
+
+ nsIContent* mContent;
+ uint32_t mAttrIdx;
+ uint32_t mAttrCount;
+};
+
+} // namespace aria
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp
new file mode 100644
index 000000000..f121835d1
--- /dev/null
+++ b/accessible/base/ARIAStateMap.cpp
@@ -0,0 +1,376 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ARIAMap.h"
+#include "nsAccUtils.h"
+#include "States.h"
+
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::aria;
+
+/**
+ * Used to store state map rule data for ARIA attribute of enum type.
+ */
+struct EnumTypeData
+{
+ // ARIA attribute name.
+ nsIAtom* const mAttrName;
+
+ // States if the attribute value is matched to the enum value. Used as
+ // nsIContent::AttrValuesArray, last item must be nullptr.
+ nsIAtom* const* const mValues[4];
+
+ // States applied if corresponding enum values are matched.
+ const uint64_t mStates[3];
+
+ // States to clear in case of match.
+ const uint64_t mClearState;
+};
+
+enum ETokenType
+{
+ eBoolType = 0,
+ eMixedType = 1, // can take 'mixed' value
+ eDefinedIfAbsent = 2 // permanent and false state are applied if absent
+};
+
+/**
+ * Used to store state map rule data for ARIA attribute of token type (including
+ * mixed value).
+ */
+struct TokenTypeData
+{
+ TokenTypeData(nsIAtom* aAttrName, uint32_t aType,
+ uint64_t aPermanentState,
+ uint64_t aTrueState,
+ uint64_t aFalseState = 0) :
+ mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState),
+ mTrueState(aTrueState), mFalseState(aFalseState)
+ { }
+
+ // ARIA attribute name.
+ nsIAtom* const mAttrName;
+
+ // Type.
+ const uint32_t mType;
+
+ // State applied if the attribute is defined or mType doesn't have
+ // eDefinedIfAbsent flag set.
+ const uint64_t mPermanentState;
+
+ // States applied if the attribute value is true/false.
+ const uint64_t mTrueState;
+ const uint64_t mFalseState;
+};
+
+/**
+ * Map enum type attribute value to accessible state.
+ */
+static void MapEnumType(dom::Element* aElement, uint64_t* aState,
+ const EnumTypeData& aData);
+
+/**
+ * Map token type attribute value to states.
+ */
+static void MapTokenType(dom::Element* aContent, uint64_t* aState,
+ const TokenTypeData& aData);
+
+bool
+aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState)
+{
+ switch (aRule) {
+ case eARIAAutoComplete:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_autocomplete,
+ { &nsGkAtoms::inlinevalue,
+ &nsGkAtoms::list,
+ &nsGkAtoms::both, nullptr },
+ { states::SUPPORTS_AUTOCOMPLETION,
+ states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
+ states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION }, 0
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIABusy:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_busy,
+ { &nsGkAtoms::_true,
+ &nsGkAtoms::error, nullptr },
+ { states::BUSY,
+ states::INVALID }, 0
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckableBool:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckableMixed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckedMixed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eMixedType,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIADisabled:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_disabled, eBoolType,
+ 0, states::UNAVAILABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAExpanded:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_expanded, eBoolType,
+ 0, states::EXPANDED, states::COLLAPSED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAHasPopup:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_haspopup, eBoolType,
+ 0, states::HASPOPUP);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAInvalid:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_invalid, eBoolType,
+ 0, states::INVALID);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAModal:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_modal, eBoolType,
+ 0, states::MODAL);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAMultiline:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent,
+ 0, states::MULTI_LINE, states::SINGLE_LINE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAMultiSelectable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_multiselectable, eBoolType,
+ 0, states::MULTISELECTABLE | states::EXTSELECTABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAOrientation:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_orientation,
+ { &nsGkAtoms::horizontal,
+ &nsGkAtoms::vertical, nullptr },
+ { states::HORIZONTAL,
+ states::VERTICAL },
+ states::HORIZONTAL | states::VERTICAL
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAPressed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_pressed, eMixedType,
+ 0, states::PRESSED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonly:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType,
+ 0, states::READONLY);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonlyOrEditable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent,
+ 0, states::READONLY, states::EDITABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonlyOrEditableIfDefined:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType,
+ 0, states::READONLY, states::EDITABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIARequired:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_required, eBoolType,
+ 0, states::REQUIRED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIASelectable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent,
+ states::SELECTABLE, states::SELECTED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIASelectableIfDefined:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_selected, eBoolType,
+ states::SELECTABLE, states::SELECTED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eReadonlyUntilEditable:
+ {
+ if (!(*aState & states::EDITABLE))
+ *aState |= states::READONLY;
+
+ return true;
+ }
+
+ case eIndeterminateIfNoValue:
+ {
+ if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
+ !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext))
+ *aState |= states::MIXED;
+
+ return true;
+ }
+
+ case eFocusableUntilDisabled:
+ {
+ if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) ||
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
+ nsGkAtoms::_false, eCaseMatters))
+ *aState |= states::FOCUSABLE;
+
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+static void
+MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData)
+{
+ switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
+ aData.mValues, eCaseMatters)) {
+ case 0:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[0];
+ return;
+ case 1:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[1];
+ return;
+ case 2:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[2];
+ return;
+ }
+}
+
+static void
+MapTokenType(dom::Element* aElement, uint64_t* aState,
+ const TokenTypeData& aData)
+{
+ if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
+ if ((aData.mType & eMixedType) &&
+ aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
+ nsGkAtoms::mixed, eCaseMatters)) {
+ *aState |= aData.mPermanentState | states::MIXED;
+ return;
+ }
+
+ if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
+ nsGkAtoms::_false, eCaseMatters)) {
+ *aState |= aData.mPermanentState | aData.mFalseState;
+ return;
+ }
+
+ *aState |= aData.mPermanentState | aData.mTrueState;
+ return;
+ }
+
+ if (aData.mType & eDefinedIfAbsent)
+ *aState |= aData.mPermanentState | aData.mFalseState;
+}
diff --git a/accessible/base/ARIAStateMap.h b/accessible/base/ARIAStateMap.h
new file mode 100644
index 000000000..41626fcaf
--- /dev/null
+++ b/accessible/base/ARIAStateMap.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _mozilla_a11y_aria_ARIAStateMap_h_
+#define _mozilla_a11y_aria_ARIAStateMap_h_
+
+#include <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,
+ eARIADisabled,
+ eARIAExpanded,
+ eARIAHasPopup,
+ eARIAInvalid,
+ eARIAModal,
+ eARIAMultiline,
+ eARIAMultiSelectable,
+ eARIAOrientation,
+ eARIAPressed,
+ eARIAReadonly,
+ eARIAReadonlyOrEditable,
+ eARIAReadonlyOrEditableIfDefined,
+ eARIARequired,
+ eARIASelectable,
+ eARIASelectableIfDefined,
+ eReadonlyUntilEditable,
+ eIndeterminateIfNoValue,
+ eFocusableUntilDisabled
+};
+
+/**
+ * Expose the accessible states for the given element accordingly to state
+ * mapping rule.
+ *
+ * @param aRule [in] state mapping rule ID
+ * @param aElement [in] node of the accessible
+ * @param aState [in/out] accessible states
+ * @return true if state map rule ID is valid
+ */
+bool MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState);
+
+} // namespace aria
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp
new file mode 100644
index 000000000..a27ceacb9
--- /dev/null
+++ b/accessible/base/AccEvent.cpp
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccEvent.h"
+
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "xpcAccEvents.h"
+#include "States.h"
+#include "xpcAccessibleDocument.h"
+
+#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/Selection.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+static_assert(static_cast<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 = EventStateManager::IsHandlingUserInput();
+ else
+ mIsFromUserInput = aIsFromUserInput == eFromUserInput ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccEvent cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AccEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AccEvent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible)
+ if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
+ tmEvent->SetNextEvent(nullptr);
+ tmEvent->SetPrevEvent(nullptr);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AccEvent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessible)
+ if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
+ CycleCollectionNoteChild(cb, tmEvent->NextEvent(), "mNext");
+ CycleCollectionNoteChild(cb, tmEvent->PrevEvent(), "mPrevEvent");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release)
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+// AccTextChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+// Note: we pass in eAllowDupes to the base class because we don't support text
+// events coalescence. We fire delayed text change events in DocAccessible but
+// we continue to base the event off the accessible object rather than just the
+// node. This means we won't try to create an accessible based on the node when
+// we are ready to fire the event and so we will no longer assert at that point
+// if the node was removed from the document. Either way, the AT won't work with
+// a defunct accessible so the behaviour should be equivalent.
+AccTextChangeEvent::
+ AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
+ const nsAString& aModifiedText, bool aIsInserted,
+ EIsFromUserInput aIsFromUserInput)
+ : AccEvent(aIsInserted ?
+ static_cast<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);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccSelChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccSelChangeEvent::
+ AccSelChangeEvent(Accessible* aWidget, Accessible* aItem,
+ SelChangeType aSelChangeType) :
+ AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange),
+ mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType),
+ mPreceedingCount(0), mPackedEvent(nullptr)
+{
+ if (aSelChangeType == eSelectionAdd) {
+ if (mWidget->GetSelectedItem(1))
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+ else
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ } else {
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccTableChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccTableChangeEvent::
+ AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType,
+ int32_t aRowOrColIndex, int32_t aNumRowsOrCols) :
+ AccEvent(aEventType, aAccessible),
+ mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccVCChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccVCChangeEvent::
+ AccVCChangeEvent(Accessible* aAccessible,
+ Accessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason, EIsFromUserInput aIsFromUserInput) :
+ AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible,
+ aIsFromUserInput),
+ mOldAccessible(aOldAccessible), mOldStart(aOldStart), mOldEnd(aOldEnd),
+ mReason(aReason)
+{
+}
+
+already_AddRefed<nsIAccessibleEvent>
+a11y::MakeXPCEvent(AccEvent* aEvent)
+{
+ DocAccessible* doc = aEvent->Document();
+ Accessible* acc = aEvent->GetAccessible();
+ nsINode* node = acc->GetNode();
+ nsIDOMNode* domNode = node ? node->AsDOMNode() : nullptr;
+ bool fromUser = aEvent->IsFromUserInput();
+ uint32_t type = aEvent->GetEventType();
+ uint32_t eventGroup = aEvent->GetEventGroups();
+ nsCOMPtr<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),
+ domNode, fromUser,
+ state, extra, sc->IsStateEnabled());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eTextChangeEvent)) {
+ AccTextChangeEvent* tc = downcast_accEvent(aEvent);
+ nsString text;
+ tc->GetModifiedText(text);
+ xpEvent = new xpcAccTextChangeEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ tc->GetStartOffset(), tc->GetLength(),
+ tc->IsTextInserted(), text);
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eHideEvent)) {
+ AccHideEvent* hideEvent = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccHideEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ ToXPC(hideEvent->TargetParent()),
+ ToXPC(hideEvent->TargetNextSibling()),
+ ToXPC(hideEvent->TargetPrevSibling()));
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eCaretMoveEvent)) {
+ AccCaretMoveEvent* cm = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccCaretMoveEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ cm->GetCaretOffset());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) {
+ AccVCChangeEvent* vcc = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccVirtualCursorChangeEvent(type,
+ ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ ToXPC(vcc->OldAccessible()),
+ vcc->OldStartOffset(),
+ vcc->OldEndOffset(),
+ vcc->Reason());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eObjectAttrChangedEvent)) {
+ AccObjectAttrChangedEvent* oac = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccObjectAttributeChangedEvent(type,
+ ToXPC(acc),
+ ToXPCDocument(doc), domNode,
+ fromUser,
+ oac->GetAttribute());
+ return xpEvent.forget();
+ }
+
+ xpEvent = new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), domNode, fromUser);
+ return xpEvent.forget();
+ }
diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h
new file mode 100644
index 000000000..416224278
--- /dev/null
+++ b/accessible/base/AccEvent.h
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _AccEvent_H_
+#define _AccEvent_H_
+
+#include "nsIAccessibleEvent.h"
+
+#include "mozilla/a11y/Accessible.h"
+
+class nsEventShell;
+namespace mozilla {
+
+namespace dom {
+class Selection;
+}
+
+namespace a11y {
+
+class DocAccessible;
+
+// Constants used to point whether the event is from user input.
+enum EIsFromUserInput
+{
+ // eNoUserInput: event is not from user input
+ eNoUserInput = 0,
+ // eFromUserInput: event is from user input
+ eFromUserInput = 1,
+ // eAutoDetect: the value should be obtained from event state manager
+ eAutoDetect = -1
+};
+
+/**
+ * Generic accessible event.
+ */
+class AccEvent
+{
+public:
+
+ // Rule for accessible events.
+ // The rule will be applied when flushing pending events.
+ enum EEventRule {
+ // eAllowDupes : More than one event of the same type is allowed.
+ // This event will always be emitted. This flag is used for events that
+ // don't support coalescence.
+ eAllowDupes,
+
+ // eCoalesceReorder : For reorder events from the same subtree or the same
+ // node, only the umbrella event on the ancestor will be emitted.
+ eCoalesceReorder,
+
+ // eCoalesceOfSameType : For events of the same type, only the newest event
+ // will be processed.
+ eCoalesceOfSameType,
+
+ // eCoalesceSelectionChange: coalescence of selection change events.
+ eCoalesceSelectionChange,
+
+ // eCoalesceStateChange: coalesce state change events.
+ eCoalesceStateChange,
+
+ // eCoalesceTextSelChange: coalescence of text selection change events.
+ eCoalesceTextSelChange,
+
+ // eRemoveDupes : For repeat events, only the newest event in queue
+ // will be emitted.
+ eRemoveDupes,
+
+ // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
+ eDoNotEmit
+ };
+
+ // Initialize with an accessible.
+ AccEvent(uint32_t aEventType, Accessible* aAccessible,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect,
+ EEventRule aEventRule = eRemoveDupes);
+
+ // AccEvent
+ uint32_t GetEventType() const { return mEventType; }
+ EEventRule GetEventRule() const { return mEventRule; }
+ bool IsFromUserInput() const { return mIsFromUserInput; }
+ EIsFromUserInput FromUserInput() const
+ { return static_cast<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
+ };
+
+ 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,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect) :
+ AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible,
+ aIsFromUserInput),
+ mCaretOffset(aCaretOffset) { }
+ virtual ~AccCaretMoveEvent() { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eCaretMoveEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eCaretMoveEvent);
+ }
+
+ // AccCaretMoveEvent
+ int32_t GetCaretOffset() const { return mCaretOffset; }
+
+private:
+ int32_t mCaretOffset;
+};
+
+
+/**
+ * Accessible text selection change event.
+ */
+class AccTextSelChangeEvent : public AccEvent
+{
+public:
+ AccTextSelChangeEvent(HyperTextAccessible* aTarget,
+ dom::Selection* aSelection,
+ int32_t aReason);
+ virtual ~AccTextSelChangeEvent();
+
+ // AccEvent
+ static const EventGroup kEventGroup = eTextSelChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent);
+ }
+
+ // AccTextSelChangeEvent
+
+ /**
+ * Return true if the text selection change wasn't caused by pure caret move.
+ */
+ bool IsCaretMoveOnly() const;
+
+private:
+ RefPtr<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,
+ int16_t aReason,
+ EIsFromUserInput aIsFromUserInput = eFromUserInput);
+
+ virtual ~AccVCChangeEvent() { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eVirtualCursorChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eVirtualCursorChangeEvent);
+ }
+
+ // AccTableChangeEvent
+ Accessible* OldAccessible() const { return mOldAccessible; }
+ int32_t OldStartOffset() const { return mOldStart; }
+ int32_t OldEndOffset() const { return mOldEnd; }
+ int32_t Reason() const { return mReason; }
+
+private:
+ RefPtr<Accessible> mOldAccessible;
+ int32_t mOldStart;
+ int32_t mOldEnd;
+ int16_t mReason;
+};
+
+/**
+ * Accessible object attribute changed event.
+ */
+class AccObjectAttrChangedEvent: public AccEvent
+{
+public:
+ AccObjectAttrChangedEvent(Accessible* aAccessible, nsIAtom* aAttribute) :
+ AccEvent(::nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, aAccessible),
+ mAttribute(aAttribute) { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eObjectAttrChangedEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eObjectAttrChangedEvent);
+ }
+
+ // AccObjectAttrChangedEvent
+ nsIAtom* GetAttribute() const { return mAttribute; }
+
+private:
+ nsCOMPtr<nsIAtom> mAttribute;
+
+ virtual ~AccObjectAttrChangedEvent() { }
+};
+
+/**
+ * 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 000000000..bef707887
--- /dev/null
+++ b/accessible/base/AccGroupInfo.cpp
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccGroupInfo.h"
+#include "nsAccUtils.h"
+
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+
+AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) :
+ mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole)
+{
+ MOZ_COUNT_CTOR(AccGroupInfo);
+ Update();
+}
+
+void
+AccGroupInfo::Update()
+{
+ Accessible* parent = mItem->Parent();
+ if (!parent)
+ return;
+
+ int32_t indexInParent = mItem->IndexInParent();
+ uint32_t siblingCount = parent->ChildCount();
+ if (indexInParent == -1 ||
+ indexInParent >= static_cast<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 sibling is not visible and hasn't the same base role.
+ if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
+ continue;
+
+ // Check if it's hierarchical flatten structure, i.e. if the sibling
+ // level is lesser than this one then group is ended, if the sibling level
+ // is greater than this one then the group is split by some child elements
+ // (group will be continued).
+ int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+ if (siblingLevel < level) {
+ mParent = sibling;
+ break;
+ }
+
+ // Skip subset.
+ if (siblingLevel > level)
+ continue;
+
+ // If the previous item in the group has calculated group information then
+ // build group information for this item based on found one.
+ if (sibling->mBits.groupInfo) {
+ mPosInSet += sibling->mBits.groupInfo->mPosInSet;
+ mParent = sibling->mBits.groupInfo->mParent;
+ mSetSize = sibling->mBits.groupInfo->mSetSize;
+ return;
+ }
+
+ mPosInSet++;
+ }
+
+ // Compute set size.
+ mSetSize = mPosInSet;
+
+ for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) {
+ Accessible* sibling = parent->GetChildAt(idx);
+
+ roles::Role siblingRole = sibling->Role();
+
+ // If the sibling is separator then the group is ended.
+ if (siblingRole == roles::SEPARATOR)
+ break;
+
+ // If sibling is visible and has the same base role
+ if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
+ continue;
+
+ // and check if it's hierarchical flatten structure.
+ int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+ if (siblingLevel < level)
+ break;
+
+ // Skip subset.
+ if (siblingLevel > level)
+ continue;
+
+ // If the next item in the group has calculated group information then
+ // build group information for this item based on found one.
+ if (sibling->mBits.groupInfo) {
+ mParent = sibling->mBits.groupInfo->mParent;
+ mSetSize = sibling->mBits.groupInfo->mSetSize;
+ return;
+ }
+
+ mSetSize++;
+ }
+
+ if (mParent)
+ return;
+
+ roles::Role parentRole = parent->Role();
+ if (ShouldReportRelations(mRole, parentRole))
+ mParent = parent;
+
+ // ARIA tree and list can be arranged by using ARIA groups to organize levels.
+ if (parentRole != roles::GROUPING)
+ return;
+
+ // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
+ // parent. In other words the parent of the tree item will be a group and
+ // the previous tree item of the group is a conceptual parent of the tree
+ // item.
+ if (mRole == roles::OUTLINEITEM) {
+ Accessible* parentPrevSibling = parent->PrevSibling();
+ if (parentPrevSibling && parentPrevSibling->Role() == mRole) {
+ mParent = parentPrevSibling;
+ return;
+ }
+ }
+
+ // Way #2 for ARIA list and tree: group is a child of an item. In other words
+ // the parent of the item will be a group and containing item of the group is
+ // a conceptual parent of the item.
+ if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) {
+ Accessible* grandParent = parent->Parent();
+ if (grandParent && grandParent->Role() == mRole)
+ mParent = grandParent;
+ }
+}
+
+Accessible*
+AccGroupInfo::FirstItemOf(Accessible* aContainer)
+{
+ // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
+ // group is a parent) or by aria-level.
+ a11y::role containerRole = aContainer->Role();
+ Accessible* item = aContainer->NextSibling();
+ if (item) {
+ if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING)
+ item = item->FirstChild();
+
+ if (item) {
+ AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
+ if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
+ return item;
+ }
+ }
+
+ // ARIA list and tree can be arranged by ARIA groups case #2 (group is
+ // a child of an item).
+ item = aContainer->LastChild();
+ if (!item)
+ return nullptr;
+
+ if (item->Role() == roles::GROUPING &&
+ (containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) {
+ item = item->FirstChild();
+ if (item) {
+ AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
+ if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
+ return item;
+ }
+ }
+
+ // Otherwise, it can be a direct child if the container is a list or tree.
+ item = aContainer->FirstChild();
+ if (ShouldReportRelations(item->Role(), containerRole))
+ return item;
+
+ return nullptr;
+}
+
+Accessible*
+AccGroupInfo::NextItemTo(Accessible* aItem)
+{
+ AccGroupInfo* groupInfo = aItem->GetGroupInfo();
+ if (!groupInfo)
+ return nullptr;
+
+ // If the item in middle of the group then search next item in siblings.
+ if (groupInfo->PosInSet() >= groupInfo->SetSize())
+ return nullptr;
+
+ Accessible* parent = aItem->Parent();
+ uint32_t childCount = parent->ChildCount();
+ for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
+ Accessible* nextItem = parent->GetChildAt(idx);
+ AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo();
+ if (nextGroupInfo &&
+ nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
+ return nextItem;
+ }
+ }
+
+ NS_NOTREACHED("Item in the middle of the group but there's no next item!");
+ return nullptr;
+}
+
+bool
+AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole)
+{
+ // We only want to report hierarchy-based node relations for items in tree or
+ // list form. ARIA level/owns relations are always reported.
+ if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
+ return true;
+ if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW)
+ return true;
+ if (aParentRole == roles::LIST && aRole == roles::LISTITEM)
+ return true;
+
+ return false;
+}
diff --git a/accessible/base/AccGroupInfo.h b/accessible/base/AccGroupInfo.h
new file mode 100644
index 000000000..093258070
--- /dev/null
+++ b/accessible/base/AccGroupInfo.h
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AccGroupInfo_h_
+#define AccGroupInfo_h_
+
+#include "Accessible-inl.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Calculate and store group information.
+ */
+class AccGroupInfo
+{
+public:
+ ~AccGroupInfo() { MOZ_COUNT_DTOR(AccGroupInfo); }
+
+ /**
+ * Return 1-based position in the group.
+ */
+ uint32_t PosInSet() const { return mPosInSet; }
+
+ /**
+ * Return a number of items in the group.
+ */
+ uint32_t SetSize() const { return mSetSize; }
+
+ /**
+ * Return a direct or logical parent of the accessible that this group info is
+ * created for.
+ */
+ Accessible* ConceptualParent() const { return mParent; }
+
+ /**
+ * Update group information.
+ */
+ void Update();
+
+ /**
+ * Create group info.
+ */
+ static AccGroupInfo* CreateGroupInfo(Accessible* aAccessible)
+ {
+ mozilla::a11y::role role = aAccessible->Role();
+ if (role != mozilla::a11y::roles::ROW &&
+ role != mozilla::a11y::roles::OUTLINEITEM &&
+ role != mozilla::a11y::roles::OPTION &&
+ role != mozilla::a11y::roles::LISTITEM &&
+ role != mozilla::a11y::roles::MENUITEM &&
+ role != mozilla::a11y::roles::COMBOBOX_OPTION &&
+ role != mozilla::a11y::roles::RICH_OPTION &&
+ role != mozilla::a11y::roles::CHECK_RICH_OPTION &&
+ role != mozilla::a11y::roles::PARENT_MENUITEM &&
+ role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
+ role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
+ role != mozilla::a11y::roles::RADIOBUTTON &&
+ role != mozilla::a11y::roles::PAGETAB)
+ return nullptr;
+
+ AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
+ return info;
+ }
+
+ /**
+ * Return a first item for the given container.
+ */
+ static Accessible* FirstItemOf(Accessible* aContainer);
+
+ /**
+ * Return next item of the same group to the given item.
+ */
+ static Accessible* NextItemTo(Accessible* aItem);
+
+protected:
+ AccGroupInfo(Accessible* aItem, a11y::role aRole);
+
+private:
+ AccGroupInfo() = delete;
+ AccGroupInfo(const AccGroupInfo&) = delete;
+ AccGroupInfo& operator =(const AccGroupInfo&) = delete;
+
+ static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole)
+ {
+ if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM ||
+ aRole == mozilla::a11y::roles::PARENT_MENUITEM ||
+ aRole == mozilla::a11y::roles::RADIO_MENU_ITEM)
+ return mozilla::a11y::roles::MENUITEM;
+
+ if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION)
+ return mozilla::a11y::roles::RICH_OPTION;
+
+ return aRole;
+ }
+
+ /**
+ * Return true if the given parent and child roles should have their node
+ * relations reported.
+ */
+ static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole);
+
+ uint32_t mPosInSet;
+ uint32_t mSetSize;
+ Accessible* mParent;
+ Accessible* mItem;
+ a11y::role mRole;
+};
+
+} // namespace mozilla
+} // namespace a11y
+
+#endif
diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp
new file mode 100644
index 000000000..f6e890c50
--- /dev/null
+++ b/accessible/base/AccIterator.cpp
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccIterator.h"
+
+#include "AccGroupInfo.h"
+#ifdef MOZ_XUL
+#include "XULTreeAccessible.h"
+#endif
+
+#include "mozilla/dom/HTMLLabelElement.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// AccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+AccIterator::AccIterator(Accessible* aAccessible,
+ filters::FilterFuncPtr aFilterFunc) :
+ mFilterFunc(aFilterFunc)
+{
+ mState = new IteratorState(aAccessible);
+}
+
+AccIterator::~AccIterator()
+{
+ while (mState) {
+ IteratorState *tmp = mState;
+ mState = tmp->mParentState;
+ delete tmp;
+ }
+}
+
+Accessible*
+AccIterator::Next()
+{
+ while (mState) {
+ Accessible* child = mState->mParent->GetChildAt(mState->mIndex++);
+ if (!child) {
+ IteratorState* tmp = mState;
+ mState = mState->mParentState;
+ delete tmp;
+
+ continue;
+ }
+
+ uint32_t result = mFilterFunc(child);
+ if (result & filters::eMatch)
+ return child;
+
+ if (!(result & filters::eSkipSubtree)) {
+ IteratorState* childState = new IteratorState(child, mState);
+ mState = childState;
+ }
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccIterator::IteratorState
+
+AccIterator::IteratorState::IteratorState(Accessible* aParent,
+ IteratorState *mParentState) :
+ mParent(aParent), mIndex(0), mParentState(mParentState)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// RelatedAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+RelatedAccIterator::
+ RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
+ nsIAtom* aRelAttr) :
+ mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr),
+ mBindingParent(nullptr), mIndex(0)
+{
+ mBindingParent = aDependentContent->GetBindingParent();
+ nsIAtom* IDAttr = mBindingParent ?
+ nsGkAtoms::anonid : nsGkAtoms::id;
+
+ nsAutoString id;
+ if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id))
+ mProviders = mDocument->mDependentIDsHash.Get(id);
+}
+
+Accessible*
+RelatedAccIterator::Next()
+{
+ if (!mProviders)
+ return nullptr;
+
+ while (mIndex < mProviders->Length()) {
+ DocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++];
+
+ // Return related accessible for the given attribute and if the provider
+ // content is in the same binding in the case of XBL usage.
+ if (provider->mRelAttr == mRelAttr) {
+ nsIContent* bindingParent = provider->mContent->GetBindingParent();
+ bool inScope = mBindingParent == bindingParent ||
+ mBindingParent == provider->mContent;
+
+ if (inScope) {
+ Accessible* related = mDocument->GetAccessible(provider->mContent);
+ if (related)
+ return related;
+
+ // If the document content is pointed by relation then return the document
+ // itself.
+ if (provider->mContent == mDocument->GetContent())
+ return mDocument;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLabelIterator::
+ HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
+ LabelFilter aFilter) :
+ mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for),
+ mAcc(aAccessible), mLabelFilter(aFilter)
+{
+}
+
+bool
+HTMLLabelIterator::IsLabel(Accessible* aLabel)
+{
+ dom::HTMLLabelElement* labelEl =
+ dom::HTMLLabelElement::FromContent(aLabel->GetContent());
+ return labelEl && labelEl->GetControl() == mAcc->GetContent();
+}
+
+Accessible*
+HTMLLabelIterator::Next()
+{
+ // Get either <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->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,
+ nsIAtom* aIDRefsAttr) :
+ mContent(aContent), mDoc(aDoc), mCurrIdx(0)
+{
+ if (mContent->IsInUncomposedDoc())
+ mContent->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;
+}
+
+nsIContent*
+IDRefsIterator::GetElem(const nsDependentSubstring& 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 (!mContent->IsInAnonymousSubtree()) {
+ dom::Element* refElm = mContent->OwnerDoc()->GetElementById(aID);
+ if (refElm || !mContent->GetXBLBinding())
+ return refElm;
+ }
+
+ // If content is in anonymous subtree or an element having anonymous subtree
+ // then use "anonid" attribute to get elements in anonymous subtree.
+
+ // Check inside the binding the element is contained in.
+ nsIContent* bindingParent = mContent->GetBindingParent();
+ if (bindingParent) {
+ nsIContent* refElm = bindingParent->OwnerDoc()->
+ GetAnonymousElementByAttribute(bindingParent, nsGkAtoms::anonid, aID);
+
+ if (refElm)
+ return refElm;
+ }
+
+ // Check inside the binding of the element.
+ if (mContent->GetXBLBinding()) {
+ return mContent->OwnerDoc()->
+ GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, aID);
+ }
+
+ return nullptr;
+}
+
+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(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 000000000..362eb3a92
--- /dev/null
+++ b/accessible/base/AccIterator.h
@@ -0,0 +1,325 @@
+/* -*- 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(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(Accessible* aParent, IteratorState* mParentState = nullptr);
+
+ 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,
+ nsIAtom* 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;
+ nsIAtom* mRelAttr;
+ DocAccessible::AttrRelProviderArray* mProviders;
+ nsIContent* mBindingParent;
+ 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,
+ nsIAtom* aIDRefsAttr);
+ virtual ~IDRefsIterator() { }
+
+ /**
+ * Return next ID.
+ */
+ const nsDependentSubstring NextID();
+
+ /**
+ * Return next element.
+ */
+ nsIContent* NextElem();
+
+ /**
+ * Return the element with the given ID.
+ */
+ nsIContent* 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(Accessible* aItemContainer) :
+ mContainer(aItemContainer), mAnchor(nullptr) { }
+ virtual ~ItemIterator() { }
+
+ virtual Accessible* Next() override;
+
+private:
+ ItemIterator() = delete;
+ ItemIterator(const ItemIterator&) = delete;
+ ItemIterator& operator = (const ItemIterator&) = delete;
+
+ Accessible* mContainer;
+ Accessible* mAnchor;
+};
+
+
+/**
+ * Used to iterate through XUL tree items of the same level.
+ */
+class XULTreeItemIterator : public AccIterable
+{
+public:
+ XULTreeItemIterator(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;
+
+ 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 000000000..856d6978e
--- /dev/null
+++ b/accessible/base/AccTypes.h
@@ -0,0 +1,94 @@
+/* -*- 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,
+ eHyperTextType,
+ eImageType,
+ eOuterDocType,
+ ePluginType,
+ eTextLeafType,
+
+ /**
+ * Other accessible types.
+ */
+ eApplicationType,
+ eHTMLOptGroupType,
+ eImageMapType,
+ eMenuPopupType,
+ eProxyType,
+ eProgressType,
+ eRootType,
+ eXULLabelType,
+ eXULListItemType,
+ eXULTabpanelsType,
+ 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 000000000..77fb44a11
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.cpp
@@ -0,0 +1,27 @@
+/* -*- 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"
+
+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();
+}
diff --git a/accessible/base/AccessibleOrProxy.h b/accessible/base/AccessibleOrProxy.h
new file mode 100644
index 000000000..0cdf825ca
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.h
@@ -0,0 +1,123 @@
+/* -*- 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) {}
+
+ 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();
+ }
+
+ return AsAccessible()->ChildCount();
+ }
+
+ /**
+ * Return the child object either an accessible or a proxied accessible at
+ * the given index.
+ */
+ AccessibleOrProxy ChildAt(uint32_t aIdx)
+ {
+ if (IsProxy()) {
+ return AsProxy()->ChildAt(aIdx);
+ }
+
+ return AsAccessible()->GetChildAt(aIdx);
+ }
+
+ /**
+ * Return the first child object.
+ */
+ AccessibleOrProxy FirstChild()
+ {
+ if (IsProxy()) {
+ return AsProxy()->FirstChild();
+ }
+
+ return AsAccessible()->FirstChild();
+ }
+
+ /**
+ * Return the first child object.
+ */
+ AccessibleOrProxy LastChild()
+ {
+ if (IsProxy()) {
+ return AsProxy()->LastChild();
+ }
+
+ return AsAccessible()->LastChild();
+ }
+
+ role Role() const
+ {
+ if (IsProxy()) {
+ return AsProxy()->Role();
+ }
+
+ return AsAccessible()->Role();
+ }
+
+ AccessibleOrProxy Parent() const;
+
+ // 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:
+ uintptr_t mBits;
+ static const uintptr_t IS_PROXY = 0x1;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/base/Asserts.cpp b/accessible/base/Asserts.cpp
new file mode 100644
index 000000000..b97a48ec4
--- /dev/null
+++ b/accessible/base/Asserts.cpp
@@ -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/. */
+
+#include "nsIAccessibleRelation.h"
+#include "nsIAccessibleRole.h"
+#include "RelationType.h"
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, 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 000000000..786ccbbe3
--- /dev/null
+++ b/accessible/base/DocManager.cpp
@@ -0,0 +1,594 @@
+/* -*- 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"
+#include "xpcAccessibleDocument.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsCURILoader.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIDOMDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgress.h"
+#include "nsCoreUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
+nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+DocManager::sRemoteXPCDocumentCache = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager
+////////////////////////////////////////////////////////////////////////////////
+
+DocManager::DocManager()
+ : mDocAccessibleCache(2), mXPCDocumentCache(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager public
+
+DocAccessible*
+DocManager::GetDocAccessible(nsIDocument* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (docAcc)
+ return docAcc;
+
+ return CreateDocOrRootAccessible(aDocument);
+}
+
+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::NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ nsIDocument* 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;
+ }
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ mXPCDocumentCache.Remove(aDocument);
+ }
+
+ mDocAccessibleCache.Remove(aDOMDocument);
+}
+
+void
+DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+{
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ doc->Shutdown();
+ sRemoteXPCDocumentCache->Remove(aDoc);
+ }
+}
+
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessible* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (!xpcDoc) {
+ xpcDoc = new xpcAccessibleDocument(aDocument);
+ mXPCDocumentCache.Put(aDocument, 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>;
+ }
+
+ doc =
+ new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+ sRemoteXPCDocumentCache->Put(aDoc, 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 =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ if (!progress)
+ return false;
+
+ progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ return true;
+}
+
+void
+DocManager::Shutdown()
+{
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ 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<nsIDocument> 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)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+DocManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ nsCOMPtr<nsIDocument> document =
+ do_QueryInterface(aEvent->InternalDOMEvent()->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(nsIDocument* 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(nsIDocument* aDocument,
+ bool aAddDOMContentLoadedListener)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ EventTarget* target = window->GetChromeEventHandler();
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'pagehide' listener");
+#endif
+
+ if (aAddDOMContentLoadedListener) {
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'DOMContentLoaded' listener");
+#endif
+ }
+}
+
+void
+DocManager::RemoveListeners(nsIDocument* aDocument)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ if (!window)
+ return;
+
+ EventTarget* target = window->GetChromeEventHandler();
+ if (!target)
+ return;
+
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+}
+
+DocAccessible*
+DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
+{
+ // Ignore hiding, resource documents and documents without docshell.
+ if (!aDocument->IsVisibleConsideringAncestors() ||
+ aDocument->IsResourceDoc() || !aDocument->IsActive())
+ return nullptr;
+
+ // Ignore documents without presshell and not having root frame.
+ nsIPresShell* presShell = aDocument->GetShell();
+ 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->GetParentDocument());
+ 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, 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());
+
+ if (IPCAccessibilityActive()) {
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
+
+ // XXX We may need to handle the case that we don't have a tab child
+ // differently. It may be that this will cause us to fail to notify
+ // the parent process about important accessible documents.
+ if (tabChild) {
+ DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
+ docAcc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ IAccessibleHolder holder(CreateHolderFromAccessible(docAcc));
+#endif
+
+ static_cast<TabChild*>(tabChild.get())->
+ SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
+#if defined(XP_WIN)
+ AccessibleWrap::GetChildIDFor(docAcc),
+ holder
+#else
+ 0, 0
+#endif
+ );
+ }
+ }
+ }
+ } 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);
+}
diff --git a/accessible/base/DocManager.h b/accessible/base/DocManager.h
new file mode 100644
index 000000000..b07b6eb01
--- /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 "nsIDocument.h"
+#include "nsIDOMEventListener.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsIPresShell.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+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(nsIDocument* aDocument);
+
+ /**
+ * Return document accessible for the given presshell.
+ */
+ DocAccessible* GetDocAccessible(const nsIPresShell* aPresShell)
+ {
+ if (!aPresShell)
+ return nullptr;
+
+ DocAccessible* doc = aPresShell->GetDocAccessible();
+ if (doc)
+ return doc;
+
+ return GetDocAccessible(aPresShell->GetDocument());
+ }
+
+ /**
+ * 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,
+ nsIDocument* aDOMDocument);
+
+ /**
+ * 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);
+
+ /**
+ * 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() { }
+
+ /**
+ * Initialize the manager.
+ */
+ bool Init();
+
+ /**
+ * Shutdown the manager.
+ */
+ void Shutdown();
+
+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(nsIDocument* aDocument,
+ uint32_t aLoadEventType);
+
+ /**
+ * Add/remove 'pagehide' and 'DOMContentLoaded' event listeners.
+ */
+ void AddListeners(nsIDocument *aDocument, bool aAddPageShowListener);
+ void RemoveListeners(nsIDocument* aDocument);
+
+ /**
+ * Create document or root accessible.
+ */
+ DocAccessible* CreateDocOrRootAccessible(nsIDocument* aDocument);
+
+ /**
+ * Clear the cache and shutdown the document accessibles.
+ */
+ void ClearDocCache();
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, DocAccessible>
+ DocAccessibleHashtable;
+ DocAccessibleHashtable mDocAccessibleCache;
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
+ XPCDocumentHashtable;
+ XPCDocumentHashtable mXPCDocumentCache;
+ static 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.
+ */
+inline DocAccessible*
+GetExistingDocAccessible(const nsIDocument* aDocument)
+{
+ nsIPresShell* ps = aDocument->GetShell();
+ return ps ? ps->GetDocAccessible() : nullptr;
+}
+
+} // 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 000000000..226d1365f
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.cpp
@@ -0,0 +1,81 @@
+/* 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 000000000..b7a189922
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.h
@@ -0,0 +1,69 @@
+/* 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 000000000..c90f0aef8
--- /dev/null
+++ b/accessible/base/EventQueue.cpp
@@ -0,0 +1,344 @@
+/* -*- 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
+
+using namespace mozilla;
+using namespace mozilla::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!");
+
+ if (!mEvents.AppendElement(aEvent))
+ return false;
+
+ // 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.
+ nsTArray<RefPtr<AccEvent> > events;
+ events.SwapElements(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;
+ }
+}
diff --git a/accessible/base/EventQueue.h b/accessible/base/EventQueue.h
new file mode 100644
index 000000000..57d1c236d
--- /dev/null
+++ b/accessible/base/EventQueue.h
@@ -0,0 +1,77 @@
+/* -*- 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 000000000..cd4a07ba8
--- /dev/null
+++ b/accessible/base/EventTree.cpp
@@ -0,0 +1,618 @@
+/* -*- 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 "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;
+ 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 = 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 = Move(nodeOwnerRef);
+ nodeOwnerRef = Move(newNode);
+ nodeOwnerRef->mNext = 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 = Move(*nodeRef);
+ insNode = insNode->mNext.get();
+
+ prevNode->mNext = 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(
+ mozilla::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 = 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 000000000..932ceaf4e
--- /dev/null
+++ b/accessible/base/EventTree.h
@@ -0,0 +1,121 @@
+/* -*- 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/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * 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 000000000..ea74e3602
--- /dev/null
+++ b/accessible/base/Filters.cpp
@@ -0,0 +1,51 @@
+/* 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)
+{
+ a11y::role role = aAccessible->Role();
+ if (role == roles::ROW)
+ return eMatch | eSkipSubtree;
+
+ // Look for rows inside rowgroup.
+ if (role == roles::GROUPING)
+ 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 000000000..8ff2bc558
--- /dev/null
+++ b/accessible/base/Filters.h
@@ -0,0 +1,50 @@
+/* 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 000000000..e3575415d
--- /dev/null
+++ b/accessible/base/FocusManager.cpp
@@ -0,0 +1,404 @@
+/* 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/EventStateManager.h"
+#include "mozilla/dom/Element.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;
+}
+
+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()) {
+ nsIDocument* 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 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)
+{
+ NS_PRECONDITION(aDocument, "No document for focused accessible!");
+ if (aDocument) {
+ RefPtr<AccEvent> event =
+ new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
+ eAutoDetect, AccEvent::eCoalesceOfSameType);
+ aDocument->FireDelayedEvent(event);
+
+#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)
+{
+ NS_PRECONDITION(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();
+ 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;
+ }
+ }
+
+ // 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);
+
+ // 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();
+ 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->GetFocusedContent();
+
+ // 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.
+ if (focusedElm) {
+ if (EventStateManager::IsRemoteTarget(focusedElm)) {
+ return nullptr;
+ }
+ return focusedElm;
+ }
+
+ // Otherwise the focus can be on DOM document.
+ nsPIDOMWindowOuter* focusedWnd = DOMFocusManager->GetFocusedWindow();
+ return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
+}
+
+nsIDocument*
+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 000000000..633f9ccb2
--- /dev/null
+++ b/accessible/base/FocusManager.h
@@ -0,0 +1,132 @@
+/* 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_
+
+class nsINode;
+class nsIDocument;
+class nsISupports;
+
+namespace mozilla {
+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 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;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // 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 node having DOM focus.
+ */
+ nsINode* FocusedDOMNode() const;
+
+ /**
+ * Return DOM document having DOM focus.
+ */
+ nsIDocument* FocusedDOMDocument() const;
+
+private:
+ RefPtr<Accessible> mActiveItem;
+ RefPtr<Accessible> mActiveARIAMenubar;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/Logging.cpp b/accessible/base/Logging.cpp
new file mode 100644
index 000000000..afc37ef85
--- /dev/null
+++ b/accessible/base/Logging.cpp
@@ -0,0 +1,1039 @@
+/* -*- 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 "nsISelectionPrivate.h"
+#include "nsTraceRefcnt.h"
+#include "nsIWebProgress.h"
+#include "prenv.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIURI.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// 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(nsIDocument* aDocumentNode)
+{
+ printf("uri: %s", aDocumentNode->GetDocumentURI()->GetSpecOrDefault().get());
+}
+
+static void
+LogDocShellState(nsIDocument* aDocumentNode)
+{
+ printf("docshell busy: ");
+
+ nsAutoCString docShellBusy;
+ nsCOMPtr<nsIDocShell> docShell = aDocumentNode->GetDocShell();
+ uint32_t 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(nsIDocument* aDocumentNode)
+{
+ if (aDocumentNode->IsActive()) {
+ bool isContent = nsCoreUtils::IsContentDocument(aDocumentNode);
+ printf("%s document", (isContent ? "content" : "chrome"));
+ } else {
+ printf("document type: [failed]");\
+ }
+}
+
+static void
+LogDocShellTree(nsIDocument* aDocumentNode)
+{
+ if (aDocumentNode->IsActive()) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ printf("docshell hierarchy, parent: %p, root: %p, is tab document: %s;",
+ static_cast<void*>(parentTreeItem), static_cast<void*>(rootTreeItem),
+ (nsCoreUtils::IsTabDocument(aDocumentNode) ? "yes" : "no"));
+ }
+}
+
+static void
+LogDocState(nsIDocument* aDocumentNode)
+{
+ const char* docState = nullptr;
+ nsIDocument::ReadyState docStateFlag = aDocumentNode->GetReadyStateEnum();
+ switch (docStateFlag) {
+ case nsIDocument::READYSTATE_UNINITIALIZED:
+ docState = "uninitialized";
+ break;
+ case nsIDocument::READYSTATE_LOADING:
+ docState = "loading";
+ break;
+ case nsIDocument::READYSTATE_INTERACTIVE:
+ docState = "interactive";
+ break;
+ case nsIDocument::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(nsIDocument* aDocumentNode)
+{
+ nsIPresShell* ps = aDocumentNode->GetShell();
+ printf("presshell: %p", static_cast<void*>(ps));
+
+ nsIScrollableFrame* sf = nullptr;
+ if (ps) {
+ printf(", is %s destroying", (ps->IsDestroying() ? "" : "not"));
+ sf = ps->GetRootScrollFrameAsScrollable();
+ }
+ printf(", root scroll frame: %p", static_cast<void*>(sf));
+}
+
+static void
+LogDocLoadGroup(nsIDocument* aDocumentNode)
+{
+ nsCOMPtr<nsILoadGroup> loadGroup = aDocumentNode->GetDocumentLoadGroup();
+ printf("load group: %p", static_cast<void*>(loadGroup));
+}
+
+static void
+LogDocParent(nsIDocument* aDocumentNode)
+{
+ nsIDocument* parentDoc = aDocumentNode->GetParentDocument();
+ 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(nsIDocument* 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_CLASSIFY_URI)
+ printf("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");
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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, aMsg);
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ nsPIDOMWindowOuter* window = nsPIDOMWindowOuter::From(DOMWindow);
+ if (!window) {
+ MsgEnd();
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> 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, nsIDocument* aDocumentNode)
+{
+ MsgBegin(sDocLoadTitle, 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, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument)
+{
+ DocAccessible* document = aDocument ?
+ aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocCreateTitle, aMsg);
+ LogDocInfo(aDocumentNode, document);
+ MsgEnd();
+}
+
+void
+logging::DocDestroy(const char* aMsg, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument)
+{
+ DocAccessible* document = aDocument ?
+ aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocDestroyTitle, 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, aMsg);
+ AccessibleNNode(aTargetDescr, aTarget);
+ MsgEnd();
+}
+
+void
+logging::FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsINode* aTargetNode)
+{
+ MsgBegin(sFocusTitle, aMsg);
+ Node(aTargetDescr, aTargetNode);
+ MsgEnd();
+}
+
+void
+logging::FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsISupports* aTargetThing)
+{
+ MsgBegin(sFocusTitle, 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(nsISelection* aSelection, DocAccessible* aDocument,
+ int16_t aReason)
+{
+ nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
+
+ int16_t type = 0;
+ privSel->GetType(&type);
+
+ const char* strType = 0;
+ if (type == nsISelectionController::SELECTION_NORMAL)
+ strType = "normal";
+ else if (type == nsISelectionController::SELECTION_SPELLCHECK)
+ 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", 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, 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.Append(NS_LITERAL_STRING(" "));
+ 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, aMsgText);
+ nsAutoString level;
+ nsINode* root = aDocument->DocumentNode();
+ do {
+ printf("%s", NS_ConvertUTF16toUTF8(level).get());
+ logging::Node("", root);
+ if (root->GetFirstChild()) {
+ level.Append(NS_LITERAL_STRING(" "));
+ 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("; %02d:%02d.%03d", 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();
+ nsIDocument* 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)
+{
+ printf(" ");
+
+ if (!aNode) {
+ printf("%s: null\n", aDescr);
+ return;
+ }
+
+ if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+ printf("%s: %p, document\n", aDescr, static_cast<void*>(aNode));
+ return;
+ }
+
+ nsINode* parentNode = aNode->GetParentNode();
+ int32_t idxInParent = parentNode ? parentNode->IndexOf(aNode) : - 1;
+
+ if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ printf("%s: %p, text node, idx in parent: %d\n",
+ aDescr, static_cast<void*>(aNode), idxInParent);
+ return;
+ }
+
+ if (!aNode->IsElement()) {
+ printf("%s: %p, not accessible node type, idx in parent: %d\n",
+ aDescr, static_cast<void*>(aNode), idxInParent);
+ return;
+ }
+
+ dom::Element* elm = aNode->AsElement();
+
+ nsAutoCString tag;
+ elm->NodeInfo()->NameAtom()->ToUTF8String(tag);
+
+ nsIAtom* idAtom = elm->GetID();
+ nsAutoCString id;
+ if (idAtom)
+ idAtom->ToUTF8String(id);
+
+ printf("%s: %p, %s@id='%s', idx in parent: %d\n",
+ aDescr, static_cast<void*>(elm), tag.get(), id.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());
+
+ nsINode* node = aAccessible->GetNode();
+ if (!node) {
+ printf(", node: null\n");
+ }
+ else if (node->IsNodeOfType(nsINode::eDOCUMENT)) {
+ printf(", document node: %p\n", static_cast<void*>(node));
+ }
+ else if (node->IsNodeOfType(nsINode::eTEXT)) {
+ printf(", text node: %p\n", static_cast<void*>(node));
+ }
+ else if (node->IsElement()) {
+ dom::Element* el = node->AsElement();
+
+ nsAutoCString tag;
+ el->NodeInfo()->NameAtom()->ToUTF8String(tag);
+
+ nsIAtom* idAtom = el->GetID();
+ nsAutoCString id;
+ if (idAtom) {
+ idAtom->ToUTF8String(id);
+ }
+
+ printf(", element node: %p, %s@id='%s'\n",
+ static_cast<void*>(el), tag.get(), id.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 nsAFlatCString& 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 000000000..a5d2c16b2
--- /dev/null
+++ b/accessible/base/Logging.h
@@ -0,0 +1,225 @@
+/* -*- 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"
+
+class nsIDocument;
+class nsINode;
+class nsIRequest;
+class nsISelection;
+class nsISupports;
+class nsIWebProgress;
+
+namespace mozilla {
+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, nsIDocument* 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, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument = nullptr);
+
+/**
+ * Log the document was destroyed.
+ */
+void DocDestroy(const char* aMsg, nsIDocument* 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(nsISelection* 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, ...);
+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, ...);
+
+/**
+ * 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 nsAFlatCString& 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 000000000..5ba1a06e6
--- /dev/null
+++ b/accessible/base/MarkupMap.h
@@ -0,0 +1,340 @@
+/* -*- 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,
+ New_HTMLLink,
+ roles::LINK)
+
+MARKUPMAP(abbr,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(acronym,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(article,
+ New_HyperText,
+ roles::DOCUMENT,
+ Attr(xmlroles, article))
+
+MARKUPMAP(aside,
+ New_HyperText,
+ roles::NOTE)
+
+MARKUPMAP(blockquote,
+ New_HyperText,
+ roles::SECTION)
+
+MARKUPMAP(dd,
+ New_HTMLDefinition,
+ roles::DEFINITION)
+
+MARKUPMAP(details,
+ New_HyperText,
+ roles::DETAILS)
+
+MARKUPMAP(div,
+ nullptr,
+ roles::SECTION)
+
+MARKUPMAP(dl,
+ New_HTMLList,
+ roles::DEFINITION_LIST)
+
+MARKUPMAP(dt,
+ New_HTMLListitem,
+ roles::TERM)
+
+MARKUPMAP(figcaption,
+ New_HTMLFigcaption,
+ roles::CAPTION)
+
+MARKUPMAP(figure,
+ New_HTMLFigure,
+ roles::FIGURE,
+ Attr(xmlroles, figure))
+
+MARKUPMAP(form,
+ New_HyperText,
+ roles::FORM)
+
+MARKUPMAP(footer,
+ New_HyperText,
+ roles::FOOTER)
+
+MARKUPMAP(header,
+ New_HyperText,
+ roles::HEADER)
+
+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(label,
+ New_HTMLLabel,
+ roles::LABEL)
+
+MARKUPMAP(legend,
+ New_HTMLLegend,
+ roles::LABEL)
+
+MARKUPMAP(li,
+ New_HTMLListitem,
+ 0)
+
+MARKUPMAP(map,
+ nullptr,
+ roles::TEXT_CONTAINER)
+
+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_,
+ New_HTMLTableAccessible,
+ roles::MATHML_TABLE,
+ AttrFromDOM(align, align),
+ AttrFromDOM(columnlines_, columnlines_),
+ AttrFromDOM(rowlines_, rowlines_))
+
+MARKUPMAP(mlabeledtr_,
+ New_HTMLTableRowAccessible,
+ roles::MATHML_LABELED_ROW)
+
+MARKUPMAP(mtr_,
+ New_HTMLTableRowAccessible,
+ roles::MATHML_TABLE_ROW)
+
+MARKUPMAP(mtd_,
+ New_HTMLTableCellAccessible,
+ roles::MATHML_CELL)
+
+MARKUPMAP(maction_,
+ New_HyperText,
+ roles::MATHML_ACTION,
+ AttrFromDOM(actiontype_, actiontype_),
+ AttrFromDOM(selection_, selection_))
+
+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::SECTION)
+
+MARKUPMAP(ol,
+ New_HTMLList,
+ roles::LIST)
+
+MARKUPMAP(option,
+ New_HTMLOption,
+ 0)
+
+MARKUPMAP(optgroup,
+ New_HTMLOptgroup,
+ 0)
+
+MARKUPMAP(output,
+ New_HTMLOutput,
+ roles::SECTION,
+ Attr(live, polite))
+
+MARKUPMAP(p,
+ nullptr,
+ roles::PARAGRAPH)
+
+MARKUPMAP(progress,
+ New_HTMLProgress,
+ 0)
+
+MARKUPMAP(q,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(section,
+ New_HyperText,
+ roles::SECTION,
+ Attr(xmlroles, region))
+
+MARKUPMAP(summary,
+ New_HTMLSummary,
+ roles::SUMMARY)
+
+MARKUPMAP(time,
+ New_HyperText,
+ 0,
+ Attr(xmlroles, time),
+ AttrFromDOM(datetime, datetime))
+
+MARKUPMAP(td,
+ New_HTMLTableHeaderCellIfScope,
+ 0)
+
+MARKUPMAP(th,
+ New_HTMLTableHeaderCell,
+ 0)
+
+MARKUPMAP(ul,
+ New_HTMLList,
+ roles::LIST)
diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp
new file mode 100644
index 000000000..73d364641
--- /dev/null
+++ b/accessible/base/NotificationController.cpp
@@ -0,0 +1,947 @@
+/* -*- 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 "TextLeafAccessible.h"
+#include "TextUpdater.h"
+
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector
+////////////////////////////////////////////////////////////////////////////////
+
+NotificationController::NotificationController(DocAccessible* aDocument,
+ nsIPresShell* 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, Flush_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)
+{
+ // 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->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
+ aEvent->GetAccessible()->ShowEventTarget()) {
+ AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
+ DropMutationEvent(showEvent);
+ 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 {
+ 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,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode)
+{
+ nsTArray<nsCOMPtr<nsIContent>>* list =
+ mContentInsertions.LookupOrAdd(aContainer);
+
+ bool needsProcessing = false;
+ nsIContent* node = aStartChildNode;
+ while (node != aEndChildNode) {
+ // Notification triggers for content insertion even if no content was
+ // actually inserted, check if the given content has a frame to discard
+ // this case early.
+ if (node->GetPrimaryFrame()) {
+ if (list->AppendElement(node))
+ needsProcessing = true;
+ }
+ node = node->GetNextSibling();
+ }
+
+ if (needsProcessing) {
+ 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, Flush_Display))
+ mObservingState = eRefreshObserving;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: protected
+
+bool
+NotificationController::IsUpdatePending()
+{
+ return mPresShell->IsLayoutFlushObserver() ||
+ mObservingState == eRefreshProcessingForUpdate ||
+ mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
+ mTextHash.Count() != 0 ||
+ !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
+}
+
+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)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer;
+
+ // 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;
+
+ if (mObservingState == eRefreshProcessing ||
+ mObservingState == eRefreshProcessingForUpdate)
+ 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!");
+ }
+
+ // Initialize scroll support if needed.
+ if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
+ mDocument->AddScrollListener();
+
+ // 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 frame then this case should
+ // have been handled already by content removal notifications.
+ nsINode* containerNode = textNode->GetParentNode();
+ if (!containerNode) {
+ NS_ASSERTION(!textAcc,
+ "Text node was removed but accessible is kept alive!");
+ continue;
+ }
+
+ nsIFrame* textFrame = textNode->GetPrimaryFrame();
+ if (!textFrame) {
+ NS_ASSERTION(!textAcc,
+ "Text node isn't rendered but accessible is kept alive!");
+ continue;
+ }
+
+ nsIContent* containerElm = containerNode->IsElement() ?
+ containerNode->AsElement() : nullptr;
+
+ nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+
+ // 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(containerElm, textNode);
+ 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
+
+ Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
+ MOZ_ASSERT(container,
+ "Text node having rendered text hasn't accessible document!");
+ 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.
+ 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;
+
+ 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(Move(mHangingChildDocuments[idx]));
+ continue;
+ }
+
+ outerDocAcc->RemoveChild(childDoc);
+ }
+
+ // Failed to bind the child document, destroy it.
+ childDoc->Shutdown();
+ }
+ }
+
+ mHangingChildDocuments.Clear();
+
+ // 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 only currently queued generic notifications.
+ nsTArray < RefPtr<Notification> > notifications;
+ notifications.SwapElements(mNotifications);
+
+ uint32_t notificationCount = notifications.Length();
+ for (uint32_t idx = 0; idx < notificationCount; idx++) {
+ notifications[idx]->Process();
+ if (!mDocument)
+ return;
+ }
+
+ // Process invalidation list of the document after all accessible tree
+ // modification are done.
+ mDocument->ProcessInvalidationList();
+
+ // We cannot rely on DOM tree to keep aria-owns relations updated. Make
+ // a validation to remove dead links.
+ mDocument->ValidateARIAOwned();
+
+ // Process relocation list.
+ for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
+ if (mRelocations[idx]->IsInDocument()) {
+ mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
+ }
+ }
+ mRelocations.Clear();
+
+ // 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 = 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();
+ uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
+ MOZ_ASSERT(id);
+ DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
+ if (ipcDoc) {
+ parentIPCDoc->SendBindChildDoc(ipcDoc, id);
+ continue;
+ }
+
+ ipcDoc = new DocAccessibleChild(childDoc);
+ childDoc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ MOZ_ASSERT(parentIPCDoc);
+ parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
+ AccessibleWrap::GetChildIDFor(childDoc));
+#else
+ nsCOMPtr<nsITabChild> tabChild =
+ do_GetInterface(mDocument->DocumentNode()->GetDocShell());
+ if (tabChild) {
+ MOZ_ASSERT(parentIPCDoc);
+ static_cast<TabChild*>(tabChild.get())->
+ SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
+ }
+#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, Flush_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, 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 000000000..a909fc63d
--- /dev/null
+++ b/accessible/base/NotificationController.h
@@ -0,0 +1,439 @@
+/* -*- 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/IndexSequence.h"
+#include "mozilla/Tuple.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefreshDriver.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+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(typename IndexSequenceFor<Args...>::Type()); }
+
+private:
+ TNotification(const TNotification&);
+ TNotification& operator = (const TNotification&);
+
+ template <size_t... Indices>
+ void ProcessHelper(IndexSequence<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, nsIPresShell* 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,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode);
+
+ /**
+ * Pend an accessible subtree relocation.
+ */
+ void ScheduleRelocation(Accessible* aOwner)
+ {
+ if (!mRelocations.Contains(aOwner) && 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 Arg>
+ inline void HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg)
+ {
+ if (!IsUpdatePending()) {
+#ifdef A11Y_LOG
+ if (mozilla::a11y::logging::IsEnabled(mozilla::a11y::logging::eNotifications))
+ mozilla::a11y::logging::Text("sync notification processing");
+#endif
+ (aInstance->*aMethod)(aArg);
+ return;
+ }
+
+ RefPtr<Notification> notification =
+ new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+ if (notification && 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 && 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();
+
+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(mozilla::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.
+ */
+ nsIPresShell* 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)) {}
+ explicit nsCOMPtrHashKey(const nsPtrHashKey<T> &aToCopy) : mKey(aToCopy.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/Platform.h b/accessible/base/Platform.h
new file mode 100644
index 000000000..25204565b
--- /dev/null
+++ b/accessible/base/Platform.h
@@ -0,0 +1,88 @@
+/* -*- 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>
+
+class nsString;
+
+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
+
+/**
+ * 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);
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset);
+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);
+} // 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 000000000..49a2c692e
--- /dev/null
+++ b/accessible/base/Relation.h
@@ -0,0 +1,108 @@
+/* -*- 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(Move(aOther.mFirstIter)), mLastIter(aOther.mLastIter)
+ {
+ aOther.mLastIter = nullptr;
+ }
+
+ Relation& operator = (Relation&& aRH)
+ {
+ mFirstIter = 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 000000000..ff2894d3b
--- /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 000000000..fb45e42c1
--- /dev/null
+++ b/accessible/base/RelationTypeMap.h
@@ -0,0 +1,154 @@
+/* -*- 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_APPLICATION,
+ "containing application",
+ ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_APPLICATION,
+ IA2_RELATION_CONTAINING_APPLICATION)
+
+RELATIONTYPE(DETAILS,
+ "details",
+ ATK_RELATION_NULL,
+ NAVRELATION_DETAILS,
+ IA2_RELATION_DETAILS)
+
+RELATIONTYPE(DETAILS_FOR,
+ "details for",
+ ATK_RELATION_NULL,
+ NAVRELATION_DETAILS_FOR,
+ IA2_RELATION_DETAILS_FOR)
+
+RELATIONTYPE(ERRORMSG,
+ "error",
+ ATK_RELATION_NULL,
+ NAVRELATION_ERROR,
+ IA2_RELATION_ERROR)
+
+RELATIONTYPE(ERRORMSG_FOR,
+ "error for",
+ ATK_RELATION_NULL,
+ NAVRELATION_ERROR_FOR,
+ IA2_RELATION_ERROR_FOR)
diff --git a/accessible/base/Role.h b/accessible/base/Role.h
new file mode 100644
index 000000000..6d76eebd7
--- /dev/null
+++ b/accessible/base/Role.h
@@ -0,0 +1,995 @@
+/* -*- 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. It is used for role="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,
+ * xul:tree cell and xul:listcell. 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; an edit control with an associated list box that
+ * provides a set of predefined choices. It is used for html:select,
+ * xul:menulist, role="combobox".
+ */
+ 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 xul:progressmeter,
+ * 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 xul:spinbuttons.
+ */
+ 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,
+
+ /**
+ * A visual frame or container which contains a view of document content.
+ * Document frames may occur within another Document instance, in which case
+ * the second document may be said to be embedded in the containing instance.
+ * HTML frames are often DOCUMENT_FRAME. Either this object, or a
+ * singleton descendant, should implement the Document interface.
+ */
+ DOCUMENT_FRAME = 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,
+
+ LAST_ROLE = SUMMARY
+};
+
+} // namespace role
+
+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 000000000..c931355ae
--- /dev/null
+++ b/accessible/base/RoleMap.h
@@ -0,0 +1,1370 @@
+/* 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 ROLE()with the following arguments:
+ * ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule)
+ */
+
+ROLE(NOTHING,
+ "nothing",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TITLEBAR,
+ "titlebar",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Irrelevant on OS X; windows are always native.
+ ROLE_SYSTEM_TITLEBAR,
+ ROLE_SYSTEM_TITLEBAR,
+ 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.
+ ROLE_SYSTEM_MENUBAR,
+ ROLE_SYSTEM_MENUBAR,
+ 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).
+ ROLE_SYSTEM_SCROLLBAR,
+ ROLE_SYSTEM_SCROLLBAR,
+ eNameFromValueRule)
+
+ROLE(GRIP,
+ "grip",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilitySplitterRole,
+ ROLE_SYSTEM_GRIP,
+ ROLE_SYSTEM_GRIP,
+ eNoNameRule)
+
+ROLE(SOUND,
+ "sound",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_SOUND,
+ ROLE_SYSTEM_SOUND,
+ eNoNameRule)
+
+ROLE(CURSOR,
+ "cursor",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CURSOR,
+ ROLE_SYSTEM_CURSOR,
+ eNoNameRule)
+
+ROLE(CARET,
+ "caret",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CARET,
+ ROLE_SYSTEM_CARET,
+ eNoNameRule)
+
+ROLE(ALERT,
+ "alert",
+ ATK_ROLE_ALERT,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_ALERT,
+ ROLE_SYSTEM_ALERT,
+ eNoNameRule)
+
+ROLE(WINDOW,
+ "window",
+ ATK_ROLE_WINDOW,
+ NSAccessibilityWindowRole, //Irrelevant on OS X; all window a11y is handled by the system.
+ ROLE_SYSTEM_WINDOW,
+ ROLE_SYSTEM_WINDOW,
+ eNoNameRule)
+
+ROLE(INTERNAL_FRAME,
+ "internal frame",
+ ATK_ROLE_INTERNAL_FRAME,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INTERNAL_FRAME,
+ eNoNameRule)
+
+ROLE(MENUPOPUP,
+ "menupopup",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole, //The parent of menuitems.
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ eNoNameRule)
+
+ROLE(MENUITEM,
+ "menuitem",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(TOOLTIP,
+ "tooltip",
+ ATK_ROLE_TOOL_TIP,
+ @"AXHelpTag", //10.4+ only, so we re-define the constant.
+ ROLE_SYSTEM_TOOLTIP,
+ ROLE_SYSTEM_TOOLTIP,
+ eNameFromSubtreeRule)
+
+ROLE(APPLICATION,
+ "application",
+ ATK_ROLE_EMBEDDED,
+ NSAccessibilityGroupRole, //Unused on OS X. the system will take care of this.
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ eNoNameRule)
+
+ROLE(DOCUMENT,
+ "document",
+ ATK_ROLE_DOCUMENT_FRAME,
+ @"AXWebArea",
+ ROLE_SYSTEM_DOCUMENT,
+ ROLE_SYSTEM_DOCUMENT,
+ 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,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(CHART,
+ "chart",
+ ATK_ROLE_CHART,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_CHART,
+ ROLE_SYSTEM_CHART,
+ eNoNameRule)
+
+ROLE(DIALOG,
+ "dialog",
+ ATK_ROLE_DIALOG,
+ NSAccessibilityWindowRole, //There's a dialog subrole.
+ ROLE_SYSTEM_DIALOG,
+ ROLE_SYSTEM_DIALOG,
+ eNoNameRule)
+
+ROLE(BORDER,
+ "border",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_BORDER,
+ ROLE_SYSTEM_BORDER,
+ eNoNameRule)
+
+ROLE(GROUPING,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(SEPARATOR,
+ "separator",
+ ATK_ROLE_SEPARATOR,
+ NSAccessibilitySplitterRole,
+ ROLE_SYSTEM_SEPARATOR,
+ ROLE_SYSTEM_SEPARATOR,
+ eNoNameRule)
+
+ROLE(TOOLBAR,
+ "toolbar",
+ ATK_ROLE_TOOL_BAR,
+ NSAccessibilityToolbarRole,
+ ROLE_SYSTEM_TOOLBAR,
+ ROLE_SYSTEM_TOOLBAR,
+ eNoNameRule)
+
+ROLE(STATUSBAR,
+ "statusbar",
+ ATK_ROLE_STATUSBAR,
+ NSAccessibilityUnknownRole, //Doesn't exist on OS X (a status bar is its parts; a progressbar, a label, etc.)
+ ROLE_SYSTEM_STATUSBAR,
+ ROLE_SYSTEM_STATUSBAR,
+ eNoNameRule)
+
+ROLE(TABLE,
+ "table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityTableRole,
+ ROLE_SYSTEM_TABLE,
+ ROLE_SYSTEM_TABLE,
+ eNoNameRule)
+
+ROLE(COLUMNHEADER,
+ "columnheader",
+ ATK_ROLE_COLUMN_HEADER,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(ROWHEADER,
+ "rowheader",
+ ATK_ROLE_ROW_HEADER,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(COLUMN,
+ "column",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityColumnRole,
+ ROLE_SYSTEM_COLUMN,
+ ROLE_SYSTEM_COLUMN,
+ eNameFromSubtreeRule)
+
+ROLE(ROW,
+ "row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityRowRole,
+ ROLE_SYSTEM_ROW,
+ ROLE_SYSTEM_ROW,
+ eNameFromSubtreeRule)
+
+ROLE(CELL,
+ "cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LINK,
+ "link",
+ ATK_ROLE_LINK,
+ @"AXLink", //10.4+ the attr first define in SDK 10.4, so we define it here too. ROLE_LINK
+ ROLE_SYSTEM_LINK,
+ ROLE_SYSTEM_LINK,
+ eNameFromSubtreeRule)
+
+ROLE(HELPBALLOON,
+ "helpballoon",
+ ATK_ROLE_UNKNOWN,
+ @"AXHelpTag",
+ ROLE_SYSTEM_HELPBALLOON,
+ ROLE_SYSTEM_HELPBALLOON,
+ eNameFromSubtreeRule)
+
+ROLE(CHARACTER,
+ "character",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CHARACTER,
+ ROLE_SYSTEM_CHARACTER,
+ eNoNameRule)
+
+ROLE(LIST,
+ "list",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LISTITEM,
+ "listitem",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(OUTLINE,
+ "outline",
+ ATK_ROLE_TREE,
+ NSAccessibilityOutlineRole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ eNoNameRule)
+
+ROLE(OUTLINEITEM,
+ "outlineitem",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityRowRole, //XXX: use OutlineRow as subrole.
+ ROLE_SYSTEM_OUTLINEITEM,
+ ROLE_SYSTEM_OUTLINEITEM,
+ eNameFromSubtreeRule)
+
+ROLE(PAGETAB,
+ "pagetab",
+ ATK_ROLE_PAGE_TAB,
+ NSAccessibilityRadioButtonRole,
+ ROLE_SYSTEM_PAGETAB,
+ ROLE_SYSTEM_PAGETAB,
+ eNameFromSubtreeRule)
+
+ROLE(PROPERTYPAGE,
+ "propertypage",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_PROPERTYPAGE,
+ ROLE_SYSTEM_PROPERTYPAGE,
+ eNoNameRule)
+
+ROLE(INDICATOR,
+ "indicator",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ eNoNameRule)
+
+ROLE(GRAPHIC,
+ "graphic",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityImageRole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ eNoNameRule)
+
+ROLE(STATICTEXT,
+ "statictext",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ eNoNameRule)
+
+ROLE(TEXT_LEAF,
+ "text leaf",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNoNameRule)
+
+ROLE(PUSHBUTTON,
+ "pushbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(CHECKBUTTON,
+ "checkbutton",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(RADIOBUTTON,
+ "radiobutton",
+ ATK_ROLE_RADIO_BUTTON,
+ NSAccessibilityRadioButtonRole,
+ ROLE_SYSTEM_RADIOBUTTON,
+ ROLE_SYSTEM_RADIOBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(COMBOBOX,
+ "combobox",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ eNameFromValueRule)
+
+ROLE(DROPLIST,
+ "droplist",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_DROPLIST,
+ ROLE_SYSTEM_DROPLIST,
+ eNoNameRule)
+
+ROLE(PROGRESSBAR,
+ "progressbar",
+ ATK_ROLE_PROGRESS_BAR,
+ NSAccessibilityProgressIndicatorRole,
+ ROLE_SYSTEM_PROGRESSBAR,
+ ROLE_SYSTEM_PROGRESSBAR,
+ eNameFromValueRule)
+
+ROLE(DIAL,
+ "dial",
+ ATK_ROLE_DIAL,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_DIAL,
+ ROLE_SYSTEM_DIAL,
+ eNoNameRule)
+
+ROLE(HOTKEYFIELD,
+ "hotkeyfield",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ eNoNameRule)
+
+ROLE(SLIDER,
+ "slider",
+ ATK_ROLE_SLIDER,
+ NSAccessibilitySliderRole,
+ ROLE_SYSTEM_SLIDER,
+ ROLE_SYSTEM_SLIDER,
+ eNameFromValueRule)
+
+ROLE(SPINBUTTON,
+ "spinbutton",
+ ATK_ROLE_SPIN_BUTTON,
+ NSAccessibilityIncrementorRole, //Subroles: Increment/Decrement.
+ ROLE_SYSTEM_SPINBUTTON,
+ ROLE_SYSTEM_SPINBUTTON,
+ eNameFromValueRule)
+
+ROLE(DIAGRAM,
+ "diagram",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_DIAGRAM,
+ ROLE_SYSTEM_DIAGRAM,
+ eNoNameRule)
+
+ROLE(ANIMATION,
+ "animation",
+ ATK_ROLE_ANIMATION,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_ANIMATION,
+ ROLE_SYSTEM_ANIMATION,
+ eNoNameRule)
+
+ROLE(EQUATION,
+ "equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(BUTTONDROPDOWN,
+ "buttondropdown",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONMENU,
+ "buttonmenu",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityMenuButtonRole,
+ ROLE_SYSTEM_BUTTONMENU,
+ ROLE_SYSTEM_BUTTONMENU,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONDROPDOWNGRID,
+ "buttondropdowngrid",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ eNameFromSubtreeRule)
+
+ROLE(WHITESPACE,
+ "whitespace",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_WHITESPACE,
+ ROLE_SYSTEM_WHITESPACE,
+ eNoNameRule)
+
+ROLE(PAGETABLIST,
+ "pagetablist",
+ ATK_ROLE_PAGE_TAB_LIST,
+ NSAccessibilityTabGroupRole,
+ ROLE_SYSTEM_PAGETABLIST,
+ ROLE_SYSTEM_PAGETABLIST,
+ eNoNameRule)
+
+ROLE(CLOCK,
+ "clock",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ ROLE_SYSTEM_CLOCK,
+ ROLE_SYSTEM_CLOCK,
+ eNoNameRule)
+
+ROLE(SPLITBUTTON,
+ "splitbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_SPLITBUTTON,
+ ROLE_SYSTEM_SPLITBUTTON,
+ eNoNameRule)
+
+ROLE(IPADDRESS,
+ "ipaddress",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_IPADDRESS,
+ ROLE_SYSTEM_IPADDRESS,
+ eNoNameRule)
+
+ROLE(ACCEL_LABEL,
+ "accel label",
+ ATK_ROLE_ACCEL_LABEL,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ eNoNameRule)
+
+ROLE(ARROW,
+ "arrow",
+ ATK_ROLE_ARROW,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ eNoNameRule)
+
+ROLE(CANVAS,
+ "canvas",
+ ATK_ROLE_CANVAS,
+ NSAccessibilityImageRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CANVAS,
+ eNoNameRule)
+
+ROLE(CHECK_MENU_ITEM,
+ "check menu item",
+ ATK_ROLE_CHECK_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_CHECK_MENU_ITEM,
+ eNameFromSubtreeRule)
+
+ROLE(COLOR_CHOOSER,
+ "color chooser",
+ ATK_ROLE_COLOR_CHOOSER,
+ NSAccessibilityColorWellRole,
+ ROLE_SYSTEM_DIALOG,
+ IA2_ROLE_COLOR_CHOOSER,
+ eNoNameRule)
+
+ROLE(DATE_EDITOR,
+ "date editor",
+ ATK_ROLE_DATE_EDITOR,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DATE_EDITOR,
+ eNoNameRule)
+
+ROLE(DESKTOP_ICON,
+ "desktop icon",
+ ATK_ROLE_DESKTOP_ICON,
+ NSAccessibilityImageRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_ICON,
+ eNoNameRule)
+
+ROLE(DESKTOP_FRAME,
+ "desktop frame",
+ ATK_ROLE_DESKTOP_FRAME,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_PANE,
+ eNoNameRule)
+
+ROLE(DIRECTORY_PANE,
+ "directory pane",
+ ATK_ROLE_DIRECTORY_PANE,
+ NSAccessibilityBrowserRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DIRECTORY_PANE,
+ eNoNameRule)
+
+ROLE(FILE_CHOOSER,
+ "file chooser",
+ ATK_ROLE_FILE_CHOOSER,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ USE_ROLE_STRING,
+ IA2_ROLE_FILE_CHOOSER,
+ eNoNameRule)
+
+ROLE(FONT_CHOOSER,
+ "font chooser",
+ ATK_ROLE_FONT_CHOOSER,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FONT_CHOOSER,
+ eNoNameRule)
+
+ROLE(CHROME_WINDOW,
+ "chrome window",
+ ATK_ROLE_FRAME,
+ NSAccessibilityGroupRole, //Contains the main Firefox UI
+ ROLE_SYSTEM_APPLICATION,
+ IA2_ROLE_FRAME,
+ eNoNameRule)
+
+ROLE(GLASS_PANE,
+ "glass pane",
+ ATK_ROLE_GLASS_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_GLASS_PANE,
+ eNoNameRule)
+
+ROLE(HTML_CONTAINER,
+ "html container",
+ ATK_ROLE_HTML_CONTAINER,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(ICON,
+ "icon",
+ ATK_ROLE_ICON,
+ NSAccessibilityImageRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_ICON,
+ eNoNameRule)
+
+ROLE(LABEL,
+ "label",
+ ATK_ROLE_LABEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_STATICTEXT,
+ IA2_ROLE_LABEL,
+ eNameFromSubtreeRule)
+
+ROLE(LAYERED_PANE,
+ "layered pane",
+ ATK_ROLE_LAYERED_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_LAYERED_PANE,
+ eNoNameRule)
+
+ROLE(OPTION_PANE,
+ "option pane",
+ ATK_ROLE_OPTION_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_OPTION_PANE,
+ eNoNameRule)
+
+ROLE(PASSWORD_TEXT,
+ "password text",
+ ATK_ROLE_PASSWORD_TEXT,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNoNameRule)
+
+ROLE(POPUP_MENU,
+ "popup menu",
+ ATK_ROLE_POPUP_MENU,
+ NSAccessibilityUnknownRole, //Unused
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ eNoNameRule)
+
+ROLE(RADIO_MENU_ITEM,
+ "radio menu item",
+ ATK_ROLE_RADIO_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_RADIO_MENU_ITEM,
+ eNameFromSubtreeRule)
+
+ROLE(ROOT_PANE,
+ "root pane",
+ ATK_ROLE_ROOT_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_ROOT_PANE,
+ eNoNameRule)
+
+ROLE(SCROLL_PANE,
+ "scroll pane",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SCROLL_PANE,
+ eNoNameRule)
+
+ROLE(SPLIT_PANE,
+ "split pane",
+ ATK_ROLE_SPLIT_PANE,
+ NSAccessibilitySplitGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SPLIT_PANE,
+ eNoNameRule)
+
+ROLE(TABLE_COLUMN_HEADER,
+ "table column header",
+ ATK_ROLE_TABLE_COLUMN_HEADER,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(TABLE_ROW_HEADER,
+ "table row header",
+ ATK_ROLE_TABLE_ROW_HEADER,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(TEAR_OFF_MENU_ITEM,
+ "tear off menu item",
+ ATK_ROLE_TEAR_OFF_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_TEAR_OFF_MENU,
+ eNameFromSubtreeRule)
+
+ROLE(TERMINAL,
+ "terminal",
+ ATK_ROLE_TERMINAL,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TERMINAL,
+ eNoNameRule)
+
+ROLE(TEXT_CONTAINER,
+ "text container",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TOGGLE_BUTTON,
+ "toggle button",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(TREE_TABLE,
+ "tree table",
+ ATK_ROLE_TREE_TABLE,
+ NSAccessibilityTableRole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ eNoNameRule)
+
+ROLE(VIEWPORT,
+ "viewport",
+ ATK_ROLE_VIEWPORT,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_PANE,
+ IA2_ROLE_VIEW_PORT,
+ eNoNameRule)
+
+ROLE(HEADER,
+ "header",
+ ATK_ROLE_HEADER,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADER,
+ eNoNameRule)
+
+ROLE(FOOTER,
+ "footer",
+ ATK_ROLE_FOOTER,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FOOTER,
+ eNoNameRule)
+
+ROLE(PARAGRAPH,
+ "paragraph",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(RULER,
+ "ruler",
+ ATK_ROLE_RULER,
+ @"AXRuler", //10.4+ only, so we re-define the constant.
+ USE_ROLE_STRING,
+ IA2_ROLE_RULER,
+ eNoNameRule)
+
+ROLE(AUTOCOMPLETE,
+ "autocomplete",
+ ATK_ROLE_AUTOCOMPLETE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ eNoNameRule)
+
+ROLE(EDITBAR,
+ "editbar",
+ ATK_ROLE_EDITBAR,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ IA2_ROLE_EDITBAR,
+ eNoNameRule)
+
+ROLE(ENTRY,
+ "entry",
+ ATK_ROLE_ENTRY,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNameFromValueRule)
+
+ROLE(CAPTION,
+ "caption",
+ ATK_ROLE_CAPTION,
+ NSAccessibilityStaticTextRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CAPTION,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(DOCUMENT_FRAME,
+ "document frame",
+ ATK_ROLE_DOCUMENT_FRAME,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(HEADING,
+ "heading",
+ ATK_ROLE_HEADING,
+ @"AXHeading",
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADING,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(PAGE,
+ "page",
+ ATK_ROLE_PAGE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PAGE,
+ eNoNameRule)
+
+ROLE(SECTION,
+ "section",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SECTION,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(REDUNDANT_OBJECT,
+ "redundant object",
+ ATK_ROLE_REDUNDANT_OBJECT,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_REDUNDANT_OBJECT,
+ eNoNameRule)
+
+ROLE(FORM,
+ "form",
+ ATK_ROLE_FORM,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FORM,
+ eNoNameRule)
+
+ROLE(IME,
+ "ime",
+ ATK_ROLE_INPUT_METHOD_WINDOW,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INPUT_METHOD_WINDOW,
+ eNoNameRule)
+
+ROLE(APP_ROOT,
+ "app root",
+ ATK_ROLE_APPLICATION,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ eNoNameRule)
+
+ROLE(PARENT_MENUITEM,
+ "parent menuitem",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(CALENDAR,
+ "calendar",
+ ATK_ROLE_CALENDAR,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_CLIENT,
+ ROLE_SYSTEM_CLIENT,
+ eNoNameRule)
+
+ROLE(COMBOBOX_LIST,
+ "combobox list",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNoNameRule)
+
+ROLE(COMBOBOX_OPTION,
+ "combobox option",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(IMAGE_MAP,
+ "image map",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ eNoNameRule)
+
+ROLE(OPTION,
+ "listbox option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(RICH_OPTION,
+ "listbox rich option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityRowRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(LISTBOX,
+ "listbox",
+ ATK_ROLE_LIST_BOX,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNoNameRule)
+
+ROLE(FLAT_EQUATION,
+ "flat equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(GRID_CELL,
+ "gridcell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ eNameFromSubtreeRule)
+
+ROLE(EMBEDDED_OBJECT,
+ "embedded object",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_EMBEDDED_OBJECT,
+ eNoNameRule)
+
+ROLE(NOTE,
+ "note",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_NOTE,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(FIGURE,
+ "figure",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(CHECK_RICH_OPTION,
+ "check rich option",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION_LIST,
+ "definitionlist",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TERM,
+ "term",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION,
+ "definition",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ eNameFromSubtreeRule)
+
+ROLE(KEY,
+ "key",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(SWITCH,
+ "switch",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_MATH,
+ "math",
+ ATK_ROLE_MATH,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(MATHML_IDENTIFIER,
+ "mathml identifier",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_NUMBER,
+ "mathml number",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_OPERATOR,
+ "mathml operator",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_TEXT,
+ "mathml text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_STRING_LITERAL,
+ "mathml string literal",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_GLYPH,
+ "mathml glyph",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_ROW,
+ "mathml row",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_FRACTION,
+ "mathml fraction",
+ ATK_ROLE_MATH_FRACTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SQUARE_ROOT,
+ "mathml square root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ROOT,
+ "mathml root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_FENCED,
+ "mathml fenced",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ENCLOSED,
+ "mathml enclosed",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STYLE,
+ "mathml style",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUB,
+ "mathml sub",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUP,
+ "mathml sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUB_SUP,
+ "mathml sub sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER,
+ "mathml under",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_OVER,
+ "mathml over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER_OVER,
+ "mathml under over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_MULTISCRIPTS,
+ "mathml multiscripts",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE,
+ "mathml table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_LABELED_ROW,
+ "mathml labeled row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE_ROW,
+ "mathml table row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_CELL,
+ "mathml cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ACTION,
+ "mathml action",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ERROR,
+ "mathml error",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK,
+ "mathml stack",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_LONG_DIVISION,
+ "mathml long division",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_GROUP,
+ "mathml stack group",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_ROW,
+ "mathml stack row",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRIES,
+ "mathml stack carries",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRY,
+ "mathml stack carry",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_LINE,
+ "mathml stack line",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(RADIO_GROUP,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityRadioGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(TEXT,
+ "text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(DETAILS,
+ "details",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(SUMMARY,
+ "summary",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
diff --git a/accessible/base/SelectionManager.cpp b/accessible/base/SelectionManager.cpp
new file mode 100644
index 000000000..30e01cbfc
--- /dev/null
+++ b/accessible/base/SelectionManager.cpp
@@ -0,0 +1,232 @@
+/* -*- 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 "nsIAccessibleTypes.h"
+#include "nsIDOMDocument.h"
+#include "nsIPresShell.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.
+ nsCOMPtr<nsISelection> normalSel = do_QueryReferent(mCurrCtrlNormalSel);
+ if (normalSel) {
+ normalSel->AsSelection()->RemoveSelectionListener(this);
+ mCurrCtrlNormalSel = nullptr;
+ }
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ nsCOMPtr<nsISelection> spellSel = do_QueryReferent(mCurrCtrlSpellSel);
+ if (spellSel) {
+ spellSel->AsSelection()->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.
+ nsCOMPtr<nsISelection> normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AsSelection()->AddSelectionListener(this);
+ mCurrCtrlNormalSel = do_GetWeakReference(normalSel);
+
+ // Register 'this' as selection listener for the spell check selection.
+ nsCOMPtr<nsISelection> spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AsSelection()->AddSelectionListener(this);
+ mCurrCtrlSpellSel = do_GetWeakReference(spellSel);
+}
+
+void
+SelectionManager::AddDocSelectionListener(nsIPresShell* 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(nsIPresShell* 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, aEvent->FromUserInput());
+ nsEventShell::FireEvent(caretMoveEvent);
+ }
+}
+
+NS_IMETHODIMP
+SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
+ nsISelection* aSelection,
+ int16_t aReason)
+{
+ if (NS_WARN_IF(!aDOMDocument) || NS_WARN_IF(!aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIDocument> documentNode(do_QueryInterface(aDOMDocument));
+ DocAccessible* document = GetAccService()->GetDocAccessible(documentNode);
+
+#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->AsSelection(), 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->GetCommonAncestor();
+
+ 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) {
+ NS_NOTREACHED("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);
+ }
+}
diff --git a/accessible/base/SelectionManager.h b/accessible/base/SelectionManager.h
new file mode 100644
index 000000000..d8e6dabbd
--- /dev/null
+++ b/accessible/base/SelectionManager.h
@@ -0,0 +1,133 @@
+/* -*- 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 "nsIFrame.h"
+#include "nsISelectionListener.h"
+
+class nsIPresShell;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+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(nsIPresShell* aPresShell);
+
+ /**
+ * Stop listening to selection events for a given document
+ */
+ void RemoveDocSelectionListener(nsIPresShell* aShell);
+
+ /**
+ * 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;
+ }
+
+protected:
+
+ SelectionManager();
+
+ /**
+ * Process DOM selection change. Fire selection and caret move events.
+ */
+ void ProcessSelectionChanged(SelData* aSelData);
+
+private:
+ int32_t mCaretOffset;
+ HyperTextAccessible* mAccWithCaret;
+ // Currently focused controls.
+ nsWeakPtr mCurrCtrlNormalSel;
+ nsWeakPtr mCurrCtrlSpellSel;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/States.h b/accessible/base/States.h
new file mode 100644
index 000000000..3f9f486f6
--- /dev/null
+++ b/accessible/base/States.h
@@ -0,0 +1,285 @@
+/* -*- 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;
+} // namespace states
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/Statistics.h b/accessible/base/Statistics.h
new file mode 100644
index 000000000..c9f1832b2
--- /dev/null
+++ b/accessible/base/Statistics.h
@@ -0,0 +1,39 @@
+/* -*- 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 000000000..d49152cba
--- /dev/null
+++ b/accessible/base/StyleInfo.cpp
@@ -0,0 +1,122 @@
+/* -*- 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, nsIPresShell* aPresShell) :
+ mElement(aElement)
+{
+ mStyleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
+ nullptr,
+ aPresShell);
+}
+
+void
+StyleInfo::Display(nsAString& aValue)
+{
+ aValue.Truncate();
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(mStyleContext->StyleDisplay()->mDisplay,
+ nsCSSProps::kDisplayKTable), aValue);
+}
+
+void
+StyleInfo::TextAlign(nsAString& aValue)
+{
+ aValue.Truncate();
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(mStyleContext->StyleText()->mTextAlign,
+ nsCSSProps::kTextAlignKTable), aValue);
+}
+
+void
+StyleInfo::TextIndent(nsAString& aValue)
+{
+ aValue.Truncate();
+
+ const nsStyleCoord& styleCoord =
+ mStyleContext->StyleText()->mTextIndent;
+
+ nscoord coordVal = 0;
+ switch (styleCoord.GetUnit()) {
+ case eStyleUnit_Coord:
+ coordVal = styleCoord.GetCoordValue();
+ aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
+ aValue.AppendLiteral("px");
+ break;
+
+ case eStyleUnit_Percent:
+ aValue.AppendFloat(styleCoord.GetPercentValue() * 100);
+ aValue.AppendLiteral("%");
+ break;
+
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Enumerated:
+ case eStyleUnit_Calc:
+ aValue.AppendLiteral("0px");
+ break;
+ }
+}
+
+void
+StyleInfo::Margin(css::Side aSide, nsAString& aValue)
+{
+ MOZ_ASSERT(mElement->GetPrimaryFrame(), " mElement->GetPrimaryFrame() needs to be valid pointer");
+ aValue.Truncate();
+
+ nscoord coordVal = mElement->GetPrimaryFrame()->GetUsedMargin().Side(aSide);
+ aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(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::FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue)
+{
+ nsCSSKeyword keyword =
+ nsCSSProps::ValueToKeywordEnum(aValue, nsCSSProps::kFontStyleKTable);
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
+
+void
+StyleInfo::FormatTextDecorationStyle(uint8_t aValue, nsAString& aFormattedValue)
+{
+ nsCSSKeyword keyword =
+ nsCSSProps::ValueToKeywordEnum(aValue,
+ nsCSSProps::kTextDecorationStyleKTable);
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
diff --git a/accessible/base/StyleInfo.h b/accessible/base/StyleInfo.h
new file mode 100644
index 000000000..c8cc03388
--- /dev/null
+++ b/accessible/base/StyleInfo.h
@@ -0,0 +1,48 @@
+/* -*- 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 "nsStyleContext.h"
+
+namespace mozilla {
+namespace a11y {
+
+class StyleInfo
+{
+public:
+ StyleInfo(dom::Element* aElement, nsIPresShell* aPresShell);
+ ~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 FormatFontStyle(const nscoord& aValue, nsAString& 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<nsStyleContext> mStyleContext;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextAttrs.cpp b/accessible/base/TextAttrs.cpp
new file mode 100644
index 000000000..0f9e4f6fe
--- /dev/null
+++ b/accessible/base/TextAttrs.cpp
@@ -0,0 +1,913 @@
+/* -*- 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 "gfxFont.h"
+#include "nsFontMetrics.h"
+#include "nsLayoutUtils.h"
+#include "nsContainerFrame.h"
+#include "HyperTextAccessible.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/gfx/2D.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
+#endif
+
+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.
+ NS_PRECONDITION(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,
+ NS_LITERAL_STRING("false"));
+ break;
+
+ case eGrammar:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("grammar"));
+ break;
+
+ case eSpelling:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("spelling"));
+ break;
+
+ case eTrue:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("true"));
+ break;
+ }
+}
+
+bool
+TextAttrsMgr::InvalidTextAttr::
+ GetValue(nsIContent* aElm, uint32_t* aValue)
+{
+ nsIContent* elm = aElm;
+ do {
+ if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) {
+ static nsIContent::AttrValuesArray tokens[] =
+ { &nsGkAtoms::_false, &nsGkAtoms::grammar, &nsGkAtoms::spelling,
+ nullptr };
+
+ int32_t idx = elm->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)
+{
+ const nsStyleBackground* styleBackground = aFrame->StyleBackground();
+
+ if (NS_GET_A(styleBackground->mBackgroundColor) > 0) {
+ *aColor = styleBackground->mBackgroundColor;
+ 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->StyleColor()->mColor;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleColor()->mColor;
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::ColorTextAttr::
+ GetValueFor(Accessible* aAccessible, nscolor* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleColor()->mColor;
+ 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 = fontEntry->FamilyName();
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontSizeTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontSizeTextAttr::
+ FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nscoord>(!aFrame)
+{
+ mDC = aRootFrame->PresContext()->DeviceContext();
+
+ mRootNativeValue = aRootFrame->StyleFont()->mSize;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mSize;
+ 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;
+ 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<nscoord>(!aFrame)
+{
+ mRootNativeValue = aRootFrame->StyleFont()->mFont.style;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mFont.style;
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::FontStyleTextAttr::
+ GetValueFor(Accessible* aAccessible, nscoord* 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 nscoord& aValue)
+{
+ nsAutoString formattedValue;
+ StyleInfo::FormatFontStyle(aValue, formattedValue);
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_style, formattedValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontWeightTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontWeightTextAttr::
+ FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<int32_t>(!aFrame)
+{
+ mRootNativeValue = GetFontWeight(aRootFrame);
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = GetFontWeight(aFrame);
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::FontWeightTextAttr::
+ GetValueFor(Accessible* aAccessible, int32_t* 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 int32_t& aValue)
+{
+ nsAutoString formattedValue;
+ formattedValue.AppendInt(aValue);
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::fontWeight, formattedValue);
+}
+
+int32_t
+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 700;
+
+ bool useFontEntryWeight = true;
+
+ // Under Linux, when gfxPangoFontGroup code is used,
+ // font->GetStyle()->weight will give the absolute weight requested of the
+ // font face. The gfxPangoFontGroup code uses the gfxFontEntry constructor
+ // which doesn't initialize the weight field.
+#if defined(MOZ_WIDGET_GTK)
+ useFontEntryWeight = gfxPlatformGtk::UseFcFontList();
+#endif
+
+ if (useFontEntryWeight) {
+ // 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
+ // of the font face used.
+ gfxFontEntry *fontEntry = font->GetFontEntry();
+ return fontEntry->Weight();
+ } else {
+ return 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);
+}
+
+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 ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// TextDecorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::TextDecorValue::
+ TextDecorValue(nsIFrame* aFrame)
+{
+ const nsStyleTextReset* textReset = aFrame->StyleTextReset();
+ mStyle = textReset->mTextDecorationStyle;
+ mColor = aFrame->StyleColor()->
+ CalcComplexColor(textReset->mTextDecorationColor);
+ mLine = textReset->mTextDecorationLine &
+ (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_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,
+ NS_LITERAL_STRING("baseline"));
+ break;
+
+ case eTextPosSub:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ NS_LITERAL_STRING("sub"));
+ break;
+
+ case eTextPosSuper:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ NS_LITERAL_STRING("super"));
+ break;
+
+ case eTextPosNone:
+ break;
+ }
+}
+
+TextAttrsMgr::TextPosValue
+TextAttrsMgr::TextPosTextAttr::
+ GetTextPosValue(nsIFrame* aFrame) const
+{
+ const nsStyleCoord& styleCoord = aFrame->StyleDisplay()->mVerticalAlign;
+ switch (styleCoord.GetUnit()) {
+ case eStyleUnit_Enumerated:
+ switch (styleCoord.GetIntValue()) {
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ return eTextPosBaseline;
+ case NS_STYLE_VERTICAL_ALIGN_SUB:
+ return eTextPosSub;
+ case NS_STYLE_VERTICAL_ALIGN_SUPER:
+ return eTextPosSuper;
+
+ // No good guess for these:
+ // NS_STYLE_VERTICAL_ALIGN_TOP
+ // NS_STYLE_VERTICAL_ALIGN_TEXT_TOP
+ // NS_STYLE_VERTICAL_ALIGN_MIDDLE
+ // NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM
+ // NS_STYLE_VERTICAL_ALIGN_BOTTOM
+ // NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE
+ // Do not expose value of text-position attribute.
+
+ default:
+ break;
+ }
+ return eTextPosNone;
+
+ case eStyleUnit_Percent:
+ {
+ float percentValue = styleCoord.GetPercentValue();
+ return percentValue > 0 ?
+ eTextPosSuper :
+ (percentValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ case eStyleUnit_Coord:
+ {
+ nscoord coordValue = styleCoord.GetCoordValue();
+ return coordValue > 0 ?
+ eTextPosSuper :
+ (coordValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Calc:
+ break;
+ }
+
+ const nsIContent* content = aFrame->GetContent();
+ if (content) {
+ 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 000000000..a8db5a1d1
--- /dev/null
+++ b/accessible/base/TextAttrs.h
@@ -0,0 +1,478 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsColor.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<nscoord>
+ {
+ public:
+ FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontStyleTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aContent, nscoord* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscoord& aValue) override;
+ };
+
+
+ /**
+ * Class is used for the work with "font-weight" text attribute.
+ */
+ class FontWeightTextAttr : public TTextAttr<int32_t>
+ {
+ public:
+ FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontWeightTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, int32_t* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const int32_t& aValue) override;
+
+ private:
+ int32_t 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() { }
+ 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 mLine & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; }
+ bool IsLineThrough() const
+ { return mLine & NS_STYLE_TEXT_DECORATION_LINE_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;
+ uint8_t 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 000000000..15bbaa235
--- /dev/null
+++ b/accessible/base/TextRange-inl.h
@@ -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/. */
+
+#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 000000000..30474d2fa
--- /dev/null
+++ b/accessible/base/TextRange.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "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();
+ }
+
+ 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
+{
+
+}
+
+void
+TextRange::Select() const
+{
+}
+
+void
+TextRange::ScrollIntoView(EHowToAlign aHow) const
+{
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 000000000..662b67e42
--- /dev/null
+++ b/accessible/base/TextRange.h
@@ -0,0 +1,270 @@
+/* -*- 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 "mozilla/Move.h"
+#include "nsCaseTreatment.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+
+ class nsIVariant;
+
+namespace mozilla {
+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() {}
+ TextRange(TextRange&& aRange) :
+ mRoot(mozilla::Move(aRange.mRoot)),
+ mStartContainer(mozilla::Move(aRange.mStartContainer)),
+ mEndContainer(mozilla::Move(aRange.mEndContainer)),
+ mStartOffset(aRange.mStartOffset), mEndOffset(aRange.mEndOffset) {}
+
+ TextRange& operator= (TextRange&& aRange)
+ {
+ mRoot = mozilla::Move(aRange.mRoot);
+ mStartContainer = mozilla::Move(aRange.mStartContainer);
+ mEndContainer = mozilla::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;
+ void Select() const;
+
+ /**
+ * Scroll the text range into view.
+ */
+ enum EHowToAlign {
+ eAlignToTop,
+ eAlignToBottom
+ };
+ void ScrollIntoView(EHowToAlign aHow) 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; }
+
+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 000000000..75cbbe7e1
--- /dev/null
+++ b/accessible/base/TextUpdater.cpp
@@ -0,0 +1,202 @@
+/* -*- 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) {
+ NS_ERROR("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;
+ }
+ NS_NOTREACHED("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 000000000..06df3890d
--- /dev/null
+++ b/accessible/base/TextUpdater.h
@@ -0,0 +1,96 @@
+/* -*- 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 000000000..8c04b5d6f
--- /dev/null
+++ b/accessible/base/TreeWalker.cpp
@@ -0,0 +1,325 @@
+/* -*- 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 |= mContext->NoXBLKids() ?
+ nsIContent::eAllButXBL : 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 |= mContext->NoXBLKids() ?
+ nsIContent::eAllButXBL : nsIContent::eAllChildren;
+
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::~TreeWalker()
+{
+ MOZ_COUNT_DTOR(TreeWalker);
+}
+
+Accessible*
+TreeWalker::Scope(nsIContent* aAnchorNode)
+{
+ Reset();
+
+ mAnchorNode = aAnchorNode;
+
+ 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->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
+ (mChildFilter & nsIContent::eAllButXBL) ?
+ childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
+
+ if (!parentNode || !parentNode->IsElement()) {
+ return false;
+ }
+
+ // If ARIA owned child.
+ Accessible* child = mDoc->GetAccessible(childNode);
+ if (child && child->IsRelocated()) {
+ 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) {
+ mPhase = eAtARIAOwns;
+ Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
+ if (child) {
+ mARIAOwnsIdx++;
+ return child;
+ }
+ 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) {
+ mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
+ mPhase = eAtARIAOwns;
+ }
+
+ if (mPhase == eAtARIAOwns) {
+ 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()
+{
+ size_t length = mStateStack.Length();
+ mStateStack.RemoveElementAt(length - 1);
+ return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
+}
diff --git a/accessible/base/TreeWalker.h b/accessible/base/TreeWalker.h
new file mode 100644
index 000000000..377a5e3b8
--- /dev/null
+++ b/accessible/base/TreeWalker.h
@@ -0,0 +1,144 @@
+/* -*- 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
+ };
+
+ /**
+ * 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);
+
+ ~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/moz.build b/accessible/base/moz.build
new file mode 100644
index 000000000..dcccc4b54
--- /dev/null
+++ b/accessible/base/moz.build
@@ -0,0 +1,114 @@
+# -*- 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',
+ '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',
+ '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',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/win',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/other',
+ ]
+
+LOCAL_INCLUDES += [
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/dom/base',
+ '/dom/xbl',
+ '/ipc/chromium/src',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/svg',
+ '/layout/xul',
+ '/layout/xul/tree/',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ 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',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/base/nsAccCache.h b/accessible/base/nsAccCache.h
new file mode 100644
index 000000000..cb39accde
--- /dev/null
+++ b/accessible/base/nsAccCache.h
@@ -0,0 +1,28 @@
+/* -*- 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_
+
+#include "xpcAccessibleDocument.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 000000000..59db53c4b
--- /dev/null
+++ b/accessible/base/nsAccUtils.cpp
@@ -0,0 +1,447 @@
+/* -*- 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 "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void
+nsAccUtils::GetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName, nsAString& aAttrValue)
+{
+ aAttrValue.Truncate();
+
+ aAttributes->GetStringProperty(nsAtomCString(aAttrName), aAttrValue);
+}
+
+void
+nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName, const nsAString& aAttrValue)
+{
+ nsAutoString oldValue;
+ aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue, oldValue);
+}
+
+void
+nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom* aAttrName, nsIAtom* 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(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(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(do_QueryInterface(aContent));
+ if (!item)
+ return 0;
+
+ nsCOMPtr<nsIDOMXULContainerElement> container;
+ item->GetParentContainer(getter_AddRefs(container));
+ if (!container)
+ return 0;
+
+ // Get level of the item.
+ int32_t level = -1;
+ while (container) {
+ level++;
+
+ nsCOMPtr<nsIDOMXULContainerElement> parentContainer;
+ container->GetParentContainer(getter_AddRefs(parentContainer));
+ parentContainer.swap(container);
+ }
+
+ return level;
+}
+
+void
+nsAccUtils::SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+ nsIContent* aStartContent,
+ dom::Element* aTopEl)
+{
+ nsAutoString live, relevant, busy;
+ nsIContent* ancestor = aStartContent;
+ while (ancestor) {
+
+ // container-relevant attribute
+ if (relevant.IsEmpty() &&
+ HasDefinedARIAToken(ancestor, nsGkAtoms::aria_relevant) &&
+ ancestor->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->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live, live);
+ } else if (role) {
+ GetLiveAttrValue(role->liveAttRule, live);
+ }
+ if (!live.IsEmpty()) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLive, live);
+ if (role) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLiveRole,
+ role->ARIARoleString());
+ }
+ }
+ }
+
+ // container-atomic attribute
+ if (ancestor->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic,
+ nsGkAtoms::_true, eCaseMatters)) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerAtomic,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // container-busy attribute
+ if (busy.IsEmpty() &&
+ HasDefinedARIAToken(ancestor, nsGkAtoms::aria_busy) &&
+ ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_busy, busy))
+ SetAccAttr(aAttributes, nsGkAtoms::containerBusy, busy);
+
+ if (ancestor == aTopEl)
+ break;
+
+ ancestor = ancestor->GetParent();
+ if (!ancestor)
+ ancestor = aTopEl; // Use <body>/<frameset>
+ }
+}
+
+bool
+nsAccUtils::HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom)
+{
+ NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
+
+ if (!aContent->HasAttr(kNameSpaceID_None, aAtom) ||
+ aContent->AttrValueIs(kNameSpaceID_None, aAtom,
+ nsGkAtoms::_empty, eCaseMatters) ||
+ aContent->AttrValueIs(kNameSpaceID_None, aAtom,
+ nsGkAtoms::_undefined, eCaseMatters)) {
+ return false;
+ }
+ return true;
+}
+
+nsIAtom*
+nsAccUtils::GetARIAToken(dom::Element* aElement, nsIAtom* aAttr)
+{
+ if (!HasDefinedARIAToken(aElement, aAttr))
+ return nsGkAtoms::_empty;
+
+ static nsIContent::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;
+}
+
+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::IsARIASelected(Accessible* aAccessible)
+{
+ return aAccessible->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+Accessible*
+nsAccUtils::TableFor(Accessible* aRow)
+{
+ if (aRow) {
+ Accessible* table = aRow->Parent();
+ if (table) {
+ roles::Role tableRole = table->Role();
+ if (tableRole == roles::GROUPING) { // if there's a rowgroup.
+ 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:
+ NS_NOTREACHED("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:
+ NS_NOTREACHED("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 = NS_LITERAL_STRING("off");
+ return true;
+ case ePoliteLiveAttr:
+ aValue = NS_LITERAL_STRING("polite");
+ 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(Accessible* aAccessible)
+{
+ roles::Role role = aAccessible->Role();
+
+ // Don't prune the tree for certain roles if the tree is more complex than
+ // a single text leaf.
+ return
+ (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::SLIDER ||
+ role == roles::PROGRESSBAR ||
+ role == roles::SEPARATOR) &&
+ aAccessible->ContentChildCount() == 1 &&
+ aAccessible->ContentChildAt(0)->IsTextLeaf();
+}
diff --git a/accessible/base/nsAccUtils.h b/accessible/base/nsAccUtils.h
new file mode 100644
index 000000000..9a9bc2590
--- /dev/null
+++ b/accessible/base/nsAccUtils.h
@@ -0,0 +1,243 @@
+/* -*- 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 "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+
+#include "nsIDocShell.h"
+#include "nsPoint.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+namespace a11y {
+
+class HyperTextAccessible;
+class DocAccessible;
+
+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,
+ nsIAtom *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,
+ nsIAtom *aAttrName,
+ const nsAString& aAttrValue);
+
+ static void SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom* aAttrName,
+ nsIAtom* 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(Accessible* aAcc);
+
+ /**
+ * Return ARIA level value or the default one if ARIA is missed for the
+ * given accessible.
+ */
+ static int32_t GetARIAOrDefaultLevel(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
+ * @param aTopContent node to end at
+ */
+ static void SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+ nsIContent* aStartContent,
+ mozilla::dom::Element* aTopEl);
+
+ /**
+ * 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, nsIAtom *aAtom);
+
+ /**
+ * Return atomic value of ARIA attribute of boolean or NMTOKEN type.
+ */
+ static nsIAtom* GetARIAToken(mozilla::dom::Element* aElement, nsIAtom* aAttr);
+
+ /**
+ * Return document accessible for the given DOM node.
+ */
+ static DocAccessible* GetDocAccessibleFor(nsINode* aNode)
+ {
+ nsIPresShell *presShell = nsCoreUtils::GetPresShellFor(aNode);
+ return GetAccService()->GetDocAccessible(presShell);
+ }
+
+ /**
+ * 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 given accessible has aria-selected="true"
+ * attribute.
+ */
+ static bool IsARIASelected(Accessible* aAccessible);
+
+ /**
+ * 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(Accessible* aAccessible);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
new file mode 100644
index 000000000..2590969a0
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -0,0 +1,1885 @@
+/* -*- 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 "nsAccUtils.h"
+#include "nsArrayUtils.h"
+#include "nsAttrName.h"
+#include "nsEventShell.h"
+#include "nsIURI.h"
+#include "OuterDocAccessible.h"
+#include "Platform.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"
+#include "xpcAccessibleDocument.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
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#include "nsImageFrame.h"
+#include "nsIObserverService.h"
+#include "nsLayoutUtils.h"
+#include "nsPluginFrame.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "nsTreeBodyFrame.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLBinding.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsDeckFrame.h"
+
+#ifdef MOZ_XUL
+#include "XULAlertAccessible.h"
+#include "XULColorPickerAccessible.h"
+#include "XULComboboxAccessible.h"
+#include "XULElementAccessibles.h"
+#include "XULFormControlAccessible.h"
+#include "XULListboxAccessibleWrap.h"
+#include "XULMenuAccessibleWrap.h"
+#include "XULSliderAccessible.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;
+
+////////////////////////////////////////////////////////////////////////////////
+// Statics
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Return true if the element must be accessible.
+ */
+static bool
+MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument)
+{
+ if (aContent->GetPrimaryFrame()->IsFocusable())
+ return true;
+
+ uint32_t attrCount = aContent->GetAttrCount();
+ for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
+ const nsAttrName* attr = aContent->GetAttrNameAt(attrIdx);
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsIAtom* attrAtom = attr->Atom();
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
+ 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(id);
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible constructors
+
+static Accessible*
+New_HTMLLink(nsIContent* aContent, Accessible* aContext)
+{
+ // Only some roles truly enjoy life as HTMLLinkAccessibles, for details
+ // see closed bug 494807.
+ const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aContent->AsElement());
+ if (roleMapEntry && roleMapEntry->role != roles::NOTHING &&
+ roleMapEntry->role != roles::LINK) {
+ return new HyperTextAccessibleWrap(aContent, aContext->Document());
+ }
+
+ return new HTMLLinkAccessible(aContent, aContext->Document());
+}
+
+static Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
+ { return new HyperTextAccessibleWrap(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLFigureAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLLegendAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLListAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLListitem(nsIContent* aContent, Accessible* aContext)
+{
+ // 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() == aContent->GetParent())
+ return new HTMLLIAccessible(aContent, aContext->Document());
+
+ return nullptr;
+}
+
+static Accessible*
+New_HTMLDefinition(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsList())
+ return new HyperTextAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+static Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLLabelAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLOutputAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLSummary(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSummaryAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableRowAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableRowAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableCellAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableCellAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableHeaderCell(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent())
+ return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+static Accessible*
+New_HTMLTableHeaderCellIfScope(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent() &&
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope))
+ return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 MarkupMapInfo sMarkupMapList[] = {
+ #include "MarkupMap.h"
+};
+
+#undef Attr
+#undef AttrFromDOM
+#undef AttrFromDOMIf
+#undef MARKUPMAP
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService
+////////////////////////////////////////////////////////////////////////////////
+
+nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nullptr;
+ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
+xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = nullptr;
+uint32_t nsAccessibilityService::gConsumers = 0;
+
+nsAccessibilityService::nsAccessibilityService() :
+ DocManager(), FocusManager(), mMarkupMaps(ArrayLength(sMarkupMapList))
+{
+}
+
+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);
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ change->GetTarget(getter_AddRefs(target));
+ nsCOMPtr<nsIContent> node(do_QueryInterface(target));
+ if (!node || !node->IsHTMLElement()) {
+ continue;
+ }
+ nsCOMPtr<nsIArray> listenerNames;
+ change->GetChangedListenerNames(getter_AddRefs(listenerNames));
+
+ uint32_t changeCount;
+ rv = listenerNames->GetLength(&changeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0 ; i < changeCount ; i++) {
+ nsCOMPtr<nsIAtom> listenerName = do_QueryElementAt(listenerNames, i);
+
+ // We are only interested in event listener changes which may
+ // make an element accessible or inaccessible.
+ if (listenerName != nsGkAtoms::onclick &&
+ listenerName != nsGkAtoms::onmousedown &&
+ listenerName != nsGkAtoms::onmouseup) {
+ continue;
+ }
+
+ nsIDocument* ownerDoc = node->OwnerDoc();
+ DocAccessible* document = GetExistingDocAccessible(ownerDoc);
+
+ // Create an accessible for a inaccessible element having click event
+ // handler.
+ if (document && !document->HasAccessible(node) &&
+ nsCoreUtils::HasClickListener(node)) {
+ nsIContent* parentEl = node->GetFlattenedTreeParent();
+ if (parentEl) {
+ document->ContentInserted(parentEl, node, node->GetNextSibling());
+ }
+ break;
+ }
+ }
+ }
+ 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)
+{
+ nsIDocument* 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);
+}
+
+Accessible*
+nsAccessibilityService::GetRootDocumentAccessible(nsIPresShell* aPresShell,
+ bool aCanCreate)
+{
+ nsIPresShell* ps = aPresShell;
+ nsIDocument* documentNode = aPresShell->GetDocument();
+ if (documentNode) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell());
+ if (treeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ if (treeItem != rootTreeItem) {
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootTreeItem));
+ ps = docShell->GetPresShell();
+ }
+
+ return aCanCreate ? GetDocAccessible(ps) : ps->GetDocAccessible();
+ }
+ }
+ return nullptr;
+}
+
+#ifdef XP_WIN
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsIContent> > > sPendingPlugins;
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsITimer> > > sPluginTimers;
+
+class PluginTimerCallBack final : public nsITimerCallback
+{
+ ~PluginTimerCallBack() {}
+
+public:
+ PluginTimerCallBack(nsIContent* aContent) : mContent(aContent) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) final
+ {
+ if (!mContent->IsInUncomposedDoc())
+ return NS_OK;
+
+ nsIPresShell* ps = mContent->OwnerDoc()->GetShell();
+ if (ps) {
+ DocAccessible* doc = ps->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;
+ }
+
+private:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+NS_IMPL_ISUPPORTS(PluginTimerCallBack, nsITimerCallback)
+#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;
+ if (NS_SUCCEEDED(aFrame->GetPluginInstance(getter_AddRefs(pluginInstance))) &&
+ pluginInstance) {
+#ifdef XP_WIN
+ if (!sPendingPlugins->Contains(aContent) &&
+ (Preferences::GetBool("accessibility.delay_plugins") ||
+ Compatibility::IsJAWS() || Compatibility::IsWE())) {
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ RefPtr<PluginTimerCallBack> cb = new PluginTimerCallBack(aContent);
+ timer->InitWithCallback(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 crazy 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(nsIPresShell* aPresShell,
+ nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame,
+ nsIFrame* aCurrentBoxFrame)
+{
+ // Ignore tabpanels elements (a deck having an accessible) since their
+ // children are accessible not depending on selected tab.
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (!document || document->HasAccessible(aDeckNode))
+ return;
+
+ 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
+
+ document->ContentRemoved(aDeckNode, 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
+
+ document->ContentInserted(aDeckNode, panelNode, panelNode->GetNextSibling());
+ }
+}
+
+void
+nsAccessibilityService::ContentRangeInserted(nsIPresShell* aPresShell,
+ nsIContent* aContainer,
+ 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", aContainer);
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ logging::Node("content", child);
+ }
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+
+ if (document) {
+ document->ContentInserted(aContainer, aStartChild, aEndChild);
+ }
+}
+
+void
+nsAccessibilityService::ContentRemoved(nsIPresShell* 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) {
+ // Flatten hierarchy may be broken at this point so we cannot get a true
+ // container by traversing up the DOM tree. Find a parent of first accessible
+ // from the subtree of the given DOM node, that'll be a container. If no
+ // accessibles in subtree then we don't care about the change.
+ Accessible* child = document->GetAccessible(aChildNode);
+ if (!child) {
+ Accessible* container = document->GetContainerAccessible(aChildNode);
+ a11y::TreeWalker walker(container ? container : document, aChildNode,
+ a11y::TreeWalker::eWalkCache);
+ child = walker.Next();
+ }
+
+ if (child) {
+ document->ContentRemoved(child->Parent(), aChildNode);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree))
+ logging::AccessibleNNode("real container", child->Parent());
+#endif
+ }
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+}
+
+void
+nsAccessibilityService::UpdateText(nsIPresShell* aPresShell,
+ nsIContent* aContent)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document)
+ document->UpdateText(aContent);
+}
+
+void
+nsAccessibilityService::TreeViewChanged(nsIPresShell* 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(nsIPresShell* 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(nsIPresShell* 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)
+{
+ nsIPresShell* presShell = aImageFrame->PresContext()->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(nsIPresShell* 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(nsIPresShell* 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(nsIPresShell* 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, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ CopyUTF8toUTF16(stringRole, aString); \
+ 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 = new DOMStringList();
+
+ uint64_t state = nsAccUtils::To64State(aState, aExtraState);
+
+ // states
+ if (state & states::UNAVAILABLE) {
+ stringStates->Add(NS_LITERAL_STRING("unavailable"));
+ }
+ if (state & states::SELECTED) {
+ stringStates->Add(NS_LITERAL_STRING("selected"));
+ }
+ if (state & states::FOCUSED) {
+ stringStates->Add(NS_LITERAL_STRING("focused"));
+ }
+ if (state & states::PRESSED) {
+ stringStates->Add(NS_LITERAL_STRING("pressed"));
+ }
+ if (state & states::CHECKED) {
+ stringStates->Add(NS_LITERAL_STRING("checked"));
+ }
+ if (state & states::MIXED) {
+ stringStates->Add(NS_LITERAL_STRING("mixed"));
+ }
+ if (state & states::READONLY) {
+ stringStates->Add(NS_LITERAL_STRING("readonly"));
+ }
+ if (state & states::HOTTRACKED) {
+ stringStates->Add(NS_LITERAL_STRING("hottracked"));
+ }
+ if (state & states::DEFAULT) {
+ stringStates->Add(NS_LITERAL_STRING("default"));
+ }
+ if (state & states::EXPANDED) {
+ stringStates->Add(NS_LITERAL_STRING("expanded"));
+ }
+ if (state & states::COLLAPSED) {
+ stringStates->Add(NS_LITERAL_STRING("collapsed"));
+ }
+ if (state & states::BUSY) {
+ stringStates->Add(NS_LITERAL_STRING("busy"));
+ }
+ if (state & states::FLOATING) {
+ stringStates->Add(NS_LITERAL_STRING("floating"));
+ }
+ if (state & states::ANIMATED) {
+ stringStates->Add(NS_LITERAL_STRING("animated"));
+ }
+ if (state & states::INVISIBLE) {
+ stringStates->Add(NS_LITERAL_STRING("invisible"));
+ }
+ if (state & states::OFFSCREEN) {
+ stringStates->Add(NS_LITERAL_STRING("offscreen"));
+ }
+ if (state & states::SIZEABLE) {
+ stringStates->Add(NS_LITERAL_STRING("sizeable"));
+ }
+ if (state & states::MOVEABLE) {
+ stringStates->Add(NS_LITERAL_STRING("moveable"));
+ }
+ if (state & states::SELFVOICING) {
+ stringStates->Add(NS_LITERAL_STRING("selfvoicing"));
+ }
+ if (state & states::FOCUSABLE) {
+ stringStates->Add(NS_LITERAL_STRING("focusable"));
+ }
+ if (state & states::SELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("selectable"));
+ }
+ if (state & states::LINKED) {
+ stringStates->Add(NS_LITERAL_STRING("linked"));
+ }
+ if (state & states::TRAVERSED) {
+ stringStates->Add(NS_LITERAL_STRING("traversed"));
+ }
+ if (state & states::MULTISELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("multiselectable"));
+ }
+ if (state & states::EXTSELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("extselectable"));
+ }
+ if (state & states::PROTECTED) {
+ stringStates->Add(NS_LITERAL_STRING("protected"));
+ }
+ if (state & states::HASPOPUP) {
+ stringStates->Add(NS_LITERAL_STRING("haspopup"));
+ }
+ if (state & states::REQUIRED) {
+ stringStates->Add(NS_LITERAL_STRING("required"));
+ }
+ if (state & states::ALERT) {
+ stringStates->Add(NS_LITERAL_STRING("alert"));
+ }
+ if (state & states::INVALID) {
+ stringStates->Add(NS_LITERAL_STRING("invalid"));
+ }
+ if (state & states::CHECKABLE) {
+ stringStates->Add(NS_LITERAL_STRING("checkable"));
+ }
+
+ // extraStates
+ if (state & states::SUPPORTS_AUTOCOMPLETION) {
+ stringStates->Add(NS_LITERAL_STRING("autocompletion"));
+ }
+ if (state & states::DEFUNCT) {
+ stringStates->Add(NS_LITERAL_STRING("defunct"));
+ }
+ if (state & states::SELECTABLE_TEXT) {
+ stringStates->Add(NS_LITERAL_STRING("selectable text"));
+ }
+ if (state & states::EDITABLE) {
+ stringStates->Add(NS_LITERAL_STRING("editable"));
+ }
+ if (state & states::ACTIVE) {
+ stringStates->Add(NS_LITERAL_STRING("active"));
+ }
+ if (state & states::MODAL) {
+ stringStates->Add(NS_LITERAL_STRING("modal"));
+ }
+ if (state & states::MULTI_LINE) {
+ stringStates->Add(NS_LITERAL_STRING("multi line"));
+ }
+ if (state & states::HORIZONTAL) {
+ stringStates->Add(NS_LITERAL_STRING("horizontal"));
+ }
+ if (state & states::OPAQUE1) {
+ stringStates->Add(NS_LITERAL_STRING("opaque"));
+ }
+ if (state & states::SINGLE_LINE) {
+ stringStates->Add(NS_LITERAL_STRING("single line"));
+ }
+ if (state & states::TRANSIENT) {
+ stringStates->Add(NS_LITERAL_STRING("transient"));
+ }
+ if (state & states::VERTICAL) {
+ stringStates->Add(NS_LITERAL_STRING("vertical"));
+ }
+ if (state & states::STALE) {
+ stringStates->Add(NS_LITERAL_STRING("stale"));
+ }
+ if (state & states::ENABLED) {
+ stringStates->Add(NS_LITERAL_STRING("enabled"));
+ }
+ if (state & states::SENSITIVE) {
+ stringStates->Add(NS_LITERAL_STRING("sensitive"));
+ }
+ if (state & states::EXPANDABLE) {
+ stringStates->Add(NS_LITERAL_STRING("expandable"));
+ }
+
+ //unknown states
+ if (!stringStates->Length()) {
+ stringStates->Add(NS_LITERAL_STRING("unknown"));
+ }
+
+ stringStates.forget(aStringStates);
+}
+
+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;
+ }
+
+ CopyUTF8toUTF16(kEventTypeNames[aEventType], aString);
+}
+
+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->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // If it's document node then ask accessible document loader for
+ // document accessible, otherwise return null.
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(aNode));
+ return GetDocAccessible(document);
+ }
+
+ // 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();
+ nsIFrame* frame = content->GetPrimaryFrame();
+
+ // Check frame and its visibility. Note, hidden frame allows visible
+ // elements in subtree.
+ if (!frame || !frame->StyleVisibility()->IsVisible()) {
+ 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->IsNodeOfType(nsINode::eTEXT)) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ // 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) {
+ // Prefer to use markup to decide if and what kind of accessible to create,
+ const MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content, 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())
+ 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;
+ }
+ }
+ }
+
+ // Accessible XBL types and deck stuff are used in XUL only currently.
+ 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;
+ }
+ }
+
+ // XBL bindings may use @role attribute to point the accessible type
+ // they belong to.
+ newAcc = CreateAccessibleByType(content, document);
+
+ // 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()) {
+ nsIAtom* frameType = frame->GetType();
+ if (frameType == nsGkAtoms::boxFrame ||
+ frameType == nsGkAtoms::scrollFrame) {
+ newAcc = new XULTabpanelAccessible(content, document);
+ }
+ }
+ }
+
+ if (!newAcc) {
+ if (content->IsSVGElement()) {
+ nsSVGPathGeometryFrame* pathGeometryFrame = do_QueryFrame(frame);
+ if (pathGeometryFrame) {
+ // 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::svg)) {
+ newAcc = new EnumRoleAccessible<roles::DIAGRAM>(content, document);
+ }
+
+ } else if (content->IsMathMLElement()) {
+ const MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content, 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);
+
+ static const char16_t kInitIndicator[] = { '1', 0 };
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
+
+ // 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(sMarkupMapList); i++)
+ mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]);
+
+#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();
+ }
+#endif // defined(XP_WIN)
+
+ gApplicationAccessible = new ApplicationAccessible();
+ }
+
+ NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
+ gApplicationAccessible->Init();
+
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::
+ AnnotateCrashReport(NS_LITERAL_CSTRING("Accessibility"),
+ NS_LITERAL_CSTRING("Active"));
+#endif
+
+#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();
+
+ 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");
+
+ gConsumers = 0;
+
+ // Remove observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+
+ static const char16_t kShutdownIndicator[] = { '0', 0 };
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
+ }
+
+ // 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;
+}
+
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
+ DocAccessible* aDoc)
+{
+ nsAutoString role;
+ nsCoreUtils::XBLBindingRole(aContent, role);
+ if (role.IsEmpty() || role.EqualsLiteral("none"))
+ return nullptr;
+
+ if (role.EqualsLiteral("outerdoc")) {
+ RefPtr<Accessible> accessible = new OuterDocAccessible(aContent, aDoc);
+ return accessible.forget();
+ }
+
+ RefPtr<Accessible> accessible;
+#ifdef MOZ_XUL
+ // XUL controls
+ if (role.EqualsLiteral("xul:alert")) {
+ accessible = new XULAlertAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:button")) {
+ accessible = new XULButtonAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:checkbox")) {
+ accessible = new XULCheckboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:colorpicker")) {
+ accessible = new XULColorPickerAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:colorpickertile")) {
+ accessible = new XULColorPickerTileAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:combobox")) {
+ accessible = new XULComboboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tabpanels")) {
+ accessible = new XULTabpanelsAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:dropmarker")) {
+ accessible = new XULDropmarkerAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:groupbox")) {
+ accessible = new XULGroupboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:image")) {
+ if (aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::onclick)) {
+ accessible = new XULToolbarButtonAccessible(aContent, aDoc);
+
+ } else {
+ // Don't include nameless images in accessible tree.
+ if (!aContent->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::tooltiptext))
+ return nullptr;
+
+ accessible = new ImageAccessibleWrap(aContent, aDoc);
+ }
+
+ } else if (role.EqualsLiteral("xul:link")) {
+ accessible = new XULLinkAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listbox")) {
+ accessible = new XULListboxAccessibleWrap(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listcell")) {
+ // Only create cells if there's more than one per row.
+ nsIContent* listItem = aContent->GetParent();
+ if (!listItem)
+ return nullptr;
+
+ for (nsIContent* child = listItem->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::listcell) && child != aContent) {
+ accessible = new XULListCellAccessibleWrap(aContent, aDoc);
+ break;
+ }
+ }
+
+ } else if (role.EqualsLiteral("xul:listhead")) {
+ accessible = new XULColumAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listheader")) {
+ accessible = new XULColumnItemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listitem")) {
+ accessible = new XULListitemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menubar")) {
+ accessible = new XULMenubarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menulist")) {
+ accessible = new XULComboboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menuitem")) {
+ accessible = new XULMenuitemAccessibleWrap(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menupopup")) {
+#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 = aContent->GetParent();
+ if (parent && parent->IsXULElement(nsGkAtoms::menu))
+ return nullptr;
+#endif
+
+ accessible = new XULMenupopupAccessible(aContent, aDoc);
+
+ } else if(role.EqualsLiteral("xul:menuseparator")) {
+ accessible = new XULMenuSeparatorAccessible(aContent, aDoc);
+
+ } else if(role.EqualsLiteral("xul:pane")) {
+ accessible = new EnumRoleAccessible<roles::PANE>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:panel")) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters))
+ accessible = new XULAlertAccessible(aContent, aDoc);
+ else
+ accessible = new EnumRoleAccessible<roles::PANE>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:progressmeter")) {
+ accessible = new XULProgressMeterAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:statusbar")) {
+ accessible = new XULStatusBarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:scale")) {
+ accessible = new XULSliderAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:radiobutton")) {
+ accessible = new XULRadioButtonAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:radiogroup")) {
+ accessible = new XULRadioGroupAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tab")) {
+ accessible = new XULTabAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tabs")) {
+ accessible = new XULTabsAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:text")) {
+ accessible = new XULLabelAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:textbox")) {
+ accessible = new EnumRoleAccessible<roles::SECTION>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:thumb")) {
+ accessible = new XULThumbAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tree")) {
+ accessible = CreateAccessibleForXULTree(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:treecolumns")) {
+ accessible = new XULTreeColumAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:treecolumnitem")) {
+ accessible = new XULColumnItemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbar")) {
+ accessible = new XULToolbarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbarseparator")) {
+ accessible = new XULToolbarSeparatorAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tooltip")) {
+ accessible = new XULTooltipAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbarbutton")) {
+ accessible = new XULToolbarButtonAccessible(aContent, aDoc);
+
+ }
+#endif // MOZ_XUL
+
+ return accessible.forget();
+}
+
+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 HTMLCheckboxAccessible(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->GetParent();
+ nsIFrame* parentFrame = parentContent->GetPrimaryFrame();
+ if (parentFrame->GetType() != nsGkAtoms::tableWrapperFrame) {
+ parentContent = parentContent->GetParent();
+ parentFrame = parentContent->GetPrimaryFrame();
+ }
+
+ if (parentFrame->GetType() == nsGkAtoms::tableWrapperFrame &&
+ table->GetContent() == parentContent) {
+ newAcc = new HTMLTableRowAccessible(aContent, document);
+ }
+ }
+ break;
+ }
+ case eHTMLTextFieldType:
+ newAcc = new HTMLTextFieldAccessible(aContent, document);
+ break;
+ case eHyperTextType:
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd))
+ 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::MarkupMapInfo* markupMap =
+ mMarkupMaps.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->AttrValueIs(kNameSpaceID_None, *info->DOMAttrName,
+ *info->DOMAttrValue, eCaseMatters)) {
+ nsAccUtils::SetAccAttr(aAttributes, *info->name, *info->DOMAttrValue);
+ }
+ continue;
+ }
+
+ nsAutoString value;
+ aContent->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(nsIDOMNode* aDOMNode)
+{
+ nsCOMPtr<nsINode> node(do_QueryInterface(aDOMNode));
+ if (!node)
+ return false;
+
+ DocAccessible* document = GetDocAccessible(node->OwnerDoc());
+ if (!document)
+ return false;
+
+ return document->HasAccessible(node);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService private (DON'T put methods here)
+
+#ifdef MOZ_XUL
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent,
+ DocAccessible* aDoc)
+{
+ nsIContent* child = nsTreeUtils::GetDescendantChild(aContent,
+ nsGkAtoms::treechildren);
+ if (!child)
+ return nullptr;
+
+ nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame());
+ if (!treeFrame)
+ return nullptr;
+
+ RefPtr<nsTreeColumns> treeCols = treeFrame->Columns();
+ int32_t count = 0;
+ treeCols->GetCount(&count);
+
+ // Outline of list accessible.
+ if (count == 1) {
+ RefPtr<Accessible> accessible =
+ new XULTreeAccessible(aContent, aDoc, treeFrame);
+ return accessible.forget();
+ }
+
+ // Table or tree table accessible.
+ RefPtr<Accessible> accessible =
+ new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame);
+ return accessible.forget();
+}
+#endif
+
+nsAccessibilityService*
+GetOrCreateAccService(uint32_t aNewConsumer)
+{
+ 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::gConsumers |= aNewConsumer;
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+void
+MaybeShutdownAccService(uint32_t aFormerConsumer)
+{
+ nsAccessibilityService* accService =
+ nsAccessibilityService::gAccessibilityService;
+
+ if (!accService || accService->IsShutdown()) {
+ return;
+ }
+
+ if (nsCoreUtils::AccEventObserversExist() ||
+ xpcAccessibilityService::IsInUse()) {
+ // Still used by XPCOM
+ nsAccessibilityService::gConsumers =
+ (nsAccessibilityService::gConsumers & ~aFormerConsumer) |
+ nsAccessibilityService::eXPCOM;
+ return;
+ }
+
+ if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
+ nsAccessibilityService::gConsumers &= ~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 int disabledState = 0xff;
+
+ if (disabledState == 0xff) {
+ disabledState = Preferences::GetInt("accessibility.force_disabled", 0);
+ if (disabledState < ePlatformIsForceEnabled)
+ disabledState = ePlatformIsForceEnabled;
+ else if (disabledState > ePlatformIsDisabled)
+ disabledState = ePlatformIsDisabled;
+ }
+
+ return (EPlatformDisabledState)disabledState;
+}
+
+}
+}
diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h
new file mode 100644
index 000000000..562150592
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.h
@@ -0,0 +1,442 @@
+/* -*- 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/Role.h"
+#include "mozilla/a11y/SelectionManager.h"
+#include "mozilla/Preferences.h"
+
+#include "nsIObserver.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIEventListenerService.h"
+#include "xpcAccessibilityService.h"
+
+class nsImageFrame;
+class nsIArray;
+class nsIPersistentProperties;
+class nsPluginFrame;
+class nsITreeView;
+
+namespace mozilla {
+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)(nsIContent* aContent, Accessible* aContext);
+
+struct MarkupAttrInfo {
+ nsIAtom** name;
+ nsIAtom** value;
+
+ nsIAtom** DOMAttrName;
+ nsIAtom** DOMAttrValue;
+};
+
+struct MarkupMapInfo {
+ nsIAtom** tag;
+ New_Accessible* new_func;
+ a11y::role role;
+ MarkupAttrInfo attrs[4];
+};
+
+} // 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(nsIPresShell* 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(nsIDOMNode* aDOMNode);
+
+ /**
+ * Get a string equivalent for an accessilbe role value.
+ */
+ void GetStringRole(uint32_t aRole, nsAString& aString);
+
+ /**
+ * Get a string equivalent for an accessible state/extra state.
+ */
+ 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 relation type.
+ */
+ void GetStringRelationType(uint32_t aRelationType, nsAString& aString);
+
+ // nsAccesibilityService
+ /**
+ * Notification used to update the accessible tree when deck panel is
+ * switched.
+ */
+ void DeckPanelSwitched(nsIPresShell* aPresShell, nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame);
+
+ /**
+ * Notification used to update the accessible tree when new content is
+ * inserted.
+ */
+ void ContentRangeInserted(nsIPresShell* aPresShell, nsIContent* aContainer,
+ nsIContent* aStartChild, nsIContent* aEndChild);
+
+ /**
+ * Notification used to update the accessible tree when content is removed.
+ */
+ void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aChild);
+
+ void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update XUL:tree accessible tree when treeview is changed.
+ */
+ void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent,
+ nsITreeView* aView);
+
+ /**
+ * Notify of input@type="element" value change.
+ */
+ void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update list bullet accessible.
+ */
+ void UpdateListBullet(nsIPresShell* 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(nsIPresShell* 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(nsIPresShell* aPresShell);
+
+ /**
+ * Recreate an accessible for the given content node in the presshell.
+ */
+ void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
+
+ // 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::MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(aContent->NodeInfo()->NameAtom());
+ return markupMap ? markupMap->role : mozilla::a11y::roles::NOTHING;
+ }
+
+ /**
+ * 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 accessible for the element having XBL bindings.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleByType(nsIContent* aContent, DocAccessible* aDoc);
+
+ /**
+ * Create an accessible whose type depends on the given frame.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleByFrameType(nsIFrame* aFrame, nsIContent* aContent,
+ Accessible* aContext);
+
+#ifdef MOZ_XUL
+ /**
+ * Create accessible for XUL tree element.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleForXULTree(nsIContent* aContent, DocAccessible* aDoc);
+#endif
+
+ /**
+ * 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 nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
+
+ friend nsAccessibilityService* GetAccService();
+ friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
+ friend void MaybeShutdownAccService(uint32_t);
+ 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()
+{
+#ifdef MOZ_B2G
+ return false;
+#else
+ return XRE_IsContentProcess() &&
+ mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true);
+#endif
+}
+
+/**
+ * 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
+};
+
+#endif
diff --git a/accessible/base/nsAccessiblePivot.cpp b/accessible/base/nsAccessiblePivot.cpp
new file mode 100644
index 000000000..4a6b9f850
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -0,0 +1,924 @@
+/* -*- 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 "xpcAccessibleDocument.h"
+
+using namespace mozilla::a11y;
+
+
+/**
+ * An object that stores a given traversal rule during the pivot movement.
+ */
+class RuleCache
+{
+public:
+ explicit RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
+ mAcceptRoles(nullptr) { }
+ ~RuleCache () {
+ if (mAcceptRoles)
+ free(mAcceptRoles);
+ }
+
+ nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
+
+private:
+ nsCOMPtr<nsIAccessibleTraversalRule> mRule;
+ uint32_t* mAcceptRoles;
+ uint32_t mAcceptRolesLength;
+ 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, 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_TEXT,
+ (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;
+
+ nsresult rv = NS_OK;
+ Accessible* accessible =
+ SearchForward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT,
+ (aArgc > 2) ? aIsFromUserInput : true);
+
+ 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;
+
+ nsresult rv = NS_OK;
+ Accessible* accessible =
+ SearchBackward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV,
+ (aArgc > 2) ? aIsFromUserInput : true);
+
+ 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);
+
+ nsresult rv = NS_OK;
+ Accessible* accessible = SearchForward(root, aRule, true, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ 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);
+
+ *aResult = false;
+ nsresult rv = NS_OK;
+ Accessible* lastAccessible = root;
+ Accessible* accessible = nullptr;
+
+ // First go to the last accessible in pre-order
+ while (lastAccessible->HasChildren())
+ lastAccessible = lastAccessible->LastChild();
+
+ // Search backwards from last accessible and find the last occurrence in the doc
+ accessible = SearchBackward(lastAccessible, aRule, true, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+
+ *aResult = false;
+
+ int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
+ Accessible* tempPosition = mPosition;
+ Accessible* root = GetActiveRoot();
+ while (true) {
+ 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 NS_OK;
+ 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 == root)
+ return NS_OK;
+
+ // 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 NS_OK;
+ tempStart = tempEnd = -1;
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundary) {
+ case CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ 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->IsText()) {
+ 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->IsText() &&
+ tempStart == static_cast<int32_t>(childAtOffset->StartOffset())) {
+ tempPosition = childAtOffset;
+ tempStart = tempEnd = -1;
+ continue;
+ }
+
+ *aResult = true;
+
+ Accessible* startPosition = mPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mPosition = tempPosition;
+ mStartOffset = tempStart;
+ mEndOffset = tempEnd;
+ NotifyOfPivotChange(startPosition, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_TEXT,
+ (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;
+
+ int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
+ Accessible* tempPosition = mPosition;
+ Accessible* root = GetActiveRoot();
+ while (true) {
+ 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 NS_OK;
+ 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 (tempStart == -1) {
+ if (tempPosition != curPosition)
+ tempStart = text == curPosition->Parent() ?
+ text->GetChildOffset(curPosition) : text->CharacterCount();
+ else
+ tempStart = 0;
+ }
+
+ // 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 == root)
+ return NS_OK;
+
+ // 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 NS_OK;
+ tempPosition = tempText;
+ tempStart = tempEnd = tempText->CharacterCount();
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundary) {
+ case CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ 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;
+ }
+
+ *aResult = true;
+
+ Accessible* startPosition = mPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mPosition = tempPosition;
+ mStartOffset = tempStart;
+ mEndOffset = tempEnd;
+
+ NotifyOfPivotChange(startPosition, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_TEXT,
+ (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 cache(aRule);
+ Accessible* match = nullptr;
+ Accessible* child = root->ChildAtPoint(aX, aY, Accessible::eDeepestChild);
+ while (child && root != child) {
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ nsresult rv = cache.ApplyFilter(child, &filtered);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignore any matching nodes that were below this one
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE)
+ match = nullptr;
+
+ // Match if no node below this is a match
+ if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) {
+ nsIntRect childRect = child->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 (aX >= childRect.x && aX < childRect.x + childRect.width &&
+ aY >= childRect.y && aY < childRect.y + childRect.height)
+ match = child;
+ }
+
+ child = child->Parent();
+ }
+
+ if (match || !aIgnoreNoMatch)
+ *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ 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 = mPosition.forget();
+ mPosition = aPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = mEndOffset = -1;
+
+ return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason,
+ aIsFromUserInput);
+}
+
+Accessible*
+nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible,
+ RuleCache& aCache,
+ uint16_t* aFilterResult,
+ nsresult* aResult)
+{
+ Accessible* matched = aAccessible;
+ *aResult = aCache.ApplyFilter(aAccessible, aFilterResult);
+
+ if (aAccessible != mRoot && aAccessible != mModalRoot) {
+ for (Accessible* temp = aAccessible->Parent();
+ temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) {
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ *aResult = aCache.ApplyFilter(temp, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
+ *aFilterResult = filtered;
+ matched = temp;
+ }
+ }
+ }
+
+ if (aAccessible == mPosition && mStartOffset != -1 && mEndOffset != -1) {
+ HyperTextAccessible* text = aAccessible->AsHyperText();
+ if (text) {
+ matched = text->GetChildAtOffset(mStartOffset);
+ }
+ }
+
+ return matched;
+}
+
+Accessible*
+nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult)
+{
+ *aResult = NS_OK;
+
+ // Initial position could be unset, in that case return null.
+ if (!aAccessible)
+ return nullptr;
+
+ RuleCache cache(aRule);
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ Accessible* accessible = AdjustStartPosition(aAccessible, cache,
+ &filtered, aResult);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ return accessible;
+ }
+
+ Accessible* root = GetActiveRoot();
+ while (accessible != root) {
+ Accessible* parent = accessible->Parent();
+ int32_t idxInParent = accessible->IndexInParent();
+ while (idxInParent > 0) {
+ if (!(accessible = parent->GetChildAt(--idxInParent)))
+ continue;
+
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ Accessible* lastChild = nullptr;
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ (lastChild = accessible->LastChild())) {
+ parent = accessible;
+ accessible = lastChild;
+ idxInParent = accessible->IndexInParent();
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ }
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ if (!(accessible = parent))
+ break;
+
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ return nullptr;
+}
+
+Accessible*
+nsAccessiblePivot::SearchForward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult)
+{
+ *aResult = NS_OK;
+
+ // Initial position could be not set, in that case begin search from root.
+ Accessible* root = GetActiveRoot();
+ Accessible* accessible = (!aAccessible) ? root : aAccessible;
+
+ RuleCache cache(aRule);
+
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ accessible = AdjustStartPosition(accessible, cache, &filtered, aResult);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
+ return accessible;
+
+ while (true) {
+ Accessible* firstChild = nullptr;
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ (firstChild = accessible->FirstChild())) {
+ accessible = firstChild;
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ Accessible* sibling = nullptr;
+ Accessible* temp = accessible;
+ do {
+ if (temp == root)
+ break;
+
+ sibling = temp->NextSibling();
+
+ if (sibling)
+ break;
+ } while ((temp = temp->Parent()));
+
+ if (!sibling)
+ break;
+
+ accessible = sibling;
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ return nullptr;
+}
+
+HyperTextAccessible*
+nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward)
+{
+ Accessible* root = GetActiveRoot();
+ Accessible* accessible = aAccessible;
+ 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 == root)
+ break;
+
+ if (temp != aAccessible && temp->IsHyperText())
+ return temp->AsHyperText();
+
+ sibling = aBackward ? temp->PrevSibling() : temp->NextSibling();
+
+ if (sibling)
+ break;
+ } while ((temp = temp->Parent()));
+
+ if (!sibling)
+ break;
+
+ accessible = sibling;
+ if (accessible->IsHyperText())
+ return accessible->AsHyperText();
+ }
+
+ return nullptr;
+}
+
+
+bool
+nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason, bool aIsFromUserInput)
+{
+ if (aOldPosition == mPosition &&
+ aOldStart == mStartOffset && aOldEnd == mEndOffset)
+ return false;
+
+ nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition); // death grip
+ nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
+ while (iter.HasMore()) {
+ nsIAccessiblePivotObserver* obs = iter.GetNext();
+ obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, aReason,
+ aIsFromUserInput);
+ }
+
+ return true;
+}
+
+nsresult
+RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
+{
+ *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (!mAcceptRoles) {
+ nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mRule->GetPreFilter(&mPreFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mPreFilter) {
+ uint64_t state = aAccessible->State();
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
+ (state & states::INVISIBLE))
+ return NS_OK;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
+ (state & states::OFFSCREEN))
+ return NS_OK;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
+ !(state & states::FOCUSABLE))
+ return NS_OK;
+
+ if (nsIAccessibleTraversalRule::PREFILTER_ARIA_HIDDEN & mPreFilter) {
+ if (aAccessible->IsARIAHidden()) {
+ *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ return NS_OK;
+ }
+ }
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
+ !(state & states::OPAQUE1)) {
+ nsIFrame* frame = aAccessible->GetFrame();
+ if (frame->StyleEffects()->mOpacity == 0.0f) {
+ *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ return NS_OK;
+ }
+ }
+ }
+
+ if (mAcceptRolesLength > 0) {
+ uint32_t accessibleRole = aAccessible->Role();
+ bool matchesRole = false;
+ for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) {
+ matchesRole = mAcceptRoles[idx] == accessibleRole;
+ if (matchesRole)
+ break;
+ }
+ if (!matchesRole)
+ return NS_OK;
+ }
+
+ return mRule->Match(ToXPC(aAccessible), aResult);
+}
diff --git a/accessible/base/nsAccessiblePivot.h b/accessible/base/nsAccessiblePivot.h
new file mode 100644
index 000000000..a0e409aa5
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.h
@@ -0,0 +1,144 @@
+/* -*- 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; }
+
+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,
+ 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);
+
+ /*
+ * Search in preorder for the first text accessible.
+ */
+ mozilla::a11y::HyperTextAccessible* SearchForText(Accessible* aAccessible,
+ bool aBackward);
+
+ /*
+ * 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 000000000..89823b223
--- /dev/null
+++ b/accessible/base/nsCoreUtils.cpp
@@ -0,0 +1,683 @@
+/* -*- 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 "nsIDocument.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsRange.h"
+#include "nsIBoxObject.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocShell.h"
+#include "nsIObserverService.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIScrollableFrame.h"
+#include "nsISelectionPrivate.h"
+#include "nsISelectionController.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "nsView.h"
+#include "nsGkAtoms.h"
+
+#include "nsComponentManagerUtils.h"
+
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLLabelElement.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsCoreUtils
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+nsCoreUtils::IsLabelWithControl(nsIContent* aContent)
+{
+ dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromContent(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(nsITreeBoxObject *aTreeBoxObj,
+ int32_t aRowIndex, nsITreeColumn *aColumn,
+ const nsAString& aPseudoElt)
+{
+ nsCOMPtr<nsIDOMElement> tcElm;
+ aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
+ if (!tcElm)
+ return;
+
+ nsCOMPtr<nsIContent> tcContent(do_QueryInterface(tcElm));
+ nsIDocument *document = tcContent->GetUncomposedDoc();
+ if (!document)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+ if (!presShell)
+ return;
+
+ // Ensure row is visible.
+ aTreeBoxObj->EnsureRowIsVisible(aRowIndex);
+
+ // Calculate x and y coordinates.
+ int32_t x = 0, y = 0, width = 0, height = 0;
+ nsresult rv = aTreeBoxObj->GetCoordsForCellItem(aRowIndex, aColumn,
+ aPseudoElt,
+ &x, &y, &width, &height);
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
+ nsCOMPtr<nsIBoxObject> tcBoxObj;
+ tcXULElm->GetBoxObject(getter_AddRefs(tcBoxObj));
+
+ int32_t tcX = 0;
+ tcBoxObj->GetX(&tcX);
+
+ int32_t tcY = 0;
+ tcBoxObj->GetY(&tcY);
+
+ // Dispatch mouse events.
+ nsWeakFrame tcFrame = tcContent->GetPrimaryFrame();
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+
+ nsPoint offset;
+ nsIWidget *rootWidget =
+ rootFrame->GetView()->GetNearestWidget(&offset);
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+
+ int32_t cnvdX = presContext->CSSPixelsToDevPixels(tcX + x + 1) +
+ presContext->AppUnitsToDevPixels(offset.x);
+ int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + y + 1) +
+ presContext->AppUnitsToDevPixels(offset.y);
+
+ // XUL is just desktop, so there is no real reason for senfing touch events.
+ DispatchMouseEvent(eMouseDown, cnvdX, cnvdY,
+ tcContent, tcFrame, presShell, rootWidget);
+
+ DispatchMouseEvent(eMouseUp, cnvdX, cnvdY,
+ tcContent, tcFrame, presShell, rootWidget);
+}
+
+void
+nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX, int32_t aY,
+ nsIContent *aContent, nsIFrame *aFrame,
+ nsIPresShell *aPresShell, nsIWidget *aRootWidget)
+{
+ WidgetMouseEvent event(true, aMessage, aRootWidget,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+ event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
+
+ event.mClickCount = 1;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.mTime = PR_IntervalNow();
+ event.inputSource = nsIDOMMouseEvent::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,
+ nsIPresShell* 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->SetTarget(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->HasAttr(kNameSpaceID_None, nsGkAtoms::accesskey))
+ return 0;
+
+ nsIPresShell* presShell = aContent->OwnerDoc()->GetShell();
+ if (!presShell)
+ return 0;
+
+ nsPresContext *presContext = presShell->GetPresContext();
+ if (!presContext)
+ return 0;
+
+ EventStateManager *esm = presContext->EventStateManager();
+ if (!esm)
+ return 0;
+
+ return esm->GetRegisteredAccessKey(aContent);
+}
+
+nsIContent *
+nsCoreUtils::GetDOMElementFor(nsIContent *aContent)
+{
+ if (aContent->IsElement())
+ return aContent;
+
+ if (aContent->IsNodeOfType(nsINode::eTEXT))
+ 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(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)
+{
+ nsIPresShell::ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+
+ return ScrollSubstringTo(aFrame, aRange, vertical, horizontal);
+}
+
+nsresult
+nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal)
+{
+ if (!aFrame)
+ return NS_ERROR_FAILURE;
+
+ nsPresContext *presContext = aFrame->PresContext();
+
+ nsCOMPtr<nsISelectionController> selCon;
+ aFrame->GetSelectionController(presContext, getter_AddRefs(selCon));
+ NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISelection> selection;
+ selCon->GetSelection(nsISelectionController::SELECTION_ACCESSIBILITY,
+ getter_AddRefs(selection));
+
+ nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(selection));
+ selection->RemoveAllRanges();
+ selection->AddRange(aRange);
+
+ privSel->ScrollIntoViewInternal(
+ nsISelectionController::SELECTION_ANCHOR_REGION,
+ true, aVertical, aHorizontal);
+
+ selection->CollapseToStart();
+
+ 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.x - frameRect.x, point.y - frameRect.y);
+
+ nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
+ scrollPoint -= deltaPoint;
+
+ scrollableFrame->ScrollTo(scrollPoint, nsIScrollableFrame::INSTANT);
+}
+
+void
+nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType,
+ nsIPresShell::ScrollAxis *aVertical,
+ nsIPresShell::ScrollAxis *aHorizontal)
+{
+ int16_t whereY, whereX;
+ nsIPresShell::WhenToScroll whenY, whenX;
+ switch (aScrollType)
+ {
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT:
+ whereY = nsIPresShell::SCROLL_TOP;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_LEFT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT:
+ whereY = nsIPresShell::SCROLL_BOTTOM;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_RIGHT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE:
+ whereY = nsIPresShell::SCROLL_TOP;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_EDGE:
+ whereY = nsIPresShell::SCROLL_BOTTOM;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_LEFT_EDGE:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_LEFT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_RIGHT_EDGE:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_RIGHT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ default:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ }
+ *aVertical = nsIPresShell::ScrollAxis(whereY, whenY);
+ *aHorizontal = nsIPresShell::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(nsIDocument *aDocument)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell for document!");
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ docShellTreeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ return !parentTreeItem;
+}
+
+bool
+nsCoreUtils::IsContentDocument(nsIDocument *aDocument)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell tree item for document!");
+
+ return (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent);
+}
+
+bool
+nsCoreUtils::IsTabDocument(nsIDocument* aDocumentNode)
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ // Tab document running in own process doesn't have parent.
+ if (XRE_IsContentProcess())
+ return !parentTreeItem;
+
+ // Parent of docshell for tab document running in chrome process is root.
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+
+ return parentTreeItem == rootTreeItem;
+}
+
+bool
+nsCoreUtils::IsErrorPage(nsIDocument *aDocument)
+{
+ nsIURI *uri = aDocument->GetDocumentURI();
+ bool isAboutScheme = false;
+ uri->SchemeIs("about", &isAboutScheme);
+ if (!isAboutScheme)
+ return false;
+
+ nsAutoCString path;
+ uri->GetPath(path);
+
+ NS_NAMED_LITERAL_CSTRING(neterror, "neterror");
+ NS_NAMED_LITERAL_CSTRING(certerror, "certerror");
+
+ return StringBeginsWith(path, neterror) || StringBeginsWith(path, certerror);
+}
+
+bool
+nsCoreUtils::GetID(nsIContent *aContent, nsAString& aID)
+{
+ return aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aID);
+}
+
+bool
+nsCoreUtils::GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr, int32_t *aUInt)
+{
+ nsAutoString value;
+ aContent->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->GetAttr(kNameSpaceID_None, nsGkAtoms::lang, aLanguage))
+ walkUp = walkUp->GetParent();
+}
+
+already_AddRefed<nsIBoxObject>
+nsCoreUtils::GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj)
+{
+ nsCOMPtr<nsIDOMElement> tcElm;
+ aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
+ nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
+ if (!tcXULElm)
+ return nullptr;
+
+ nsCOMPtr<nsIBoxObject> boxObj;
+ tcXULElm->GetBoxObject(getter_AddRefs(boxObj));
+ return boxObj.forget();
+}
+
+already_AddRefed<nsITreeBoxObject>
+nsCoreUtils::GetTreeBoxObject(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)) {
+ // We will get the nsITreeBoxObject from the tree node
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(currentContent));
+ if (xulElement) {
+ nsCOMPtr<nsIBoxObject> box;
+ xulElement->GetBoxObject(getter_AddRefs(box));
+ nsCOMPtr<nsITreeBoxObject> treeBox(do_QueryInterface(box));
+ if (treeBox)
+ return treeBox.forget();
+ }
+ }
+ currentContent = currentContent->GetFlattenedTreeParent();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetFirstSensibleColumn(nsITreeBoxObject *aTree)
+{
+ nsCOMPtr<nsITreeColumns> cols;
+ aTree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return nullptr;
+
+ nsCOMPtr<nsITreeColumn> column;
+ cols->GetFirstColumn(getter_AddRefs(column));
+ if (column && IsColumnHidden(column))
+ return GetNextSensibleColumn(column);
+
+ return column.forget();
+}
+
+uint32_t
+nsCoreUtils::GetSensibleColumnCount(nsITreeBoxObject *aTree)
+{
+ uint32_t count = 0;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ aTree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return count;
+
+ nsCOMPtr<nsITreeColumn> column;
+ cols->GetFirstColumn(getter_AddRefs(column));
+
+ while (column) {
+ if (!IsColumnHidden(column))
+ count++;
+
+ nsCOMPtr<nsITreeColumn> nextColumn;
+ column->GetNext(getter_AddRefs(nextColumn));
+ column.swap(nextColumn);
+ }
+
+ return count;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex)
+{
+ uint32_t idx = aIndex;
+
+ nsCOMPtr<nsITreeColumn> column = GetFirstSensibleColumn(aTree);
+ while (column) {
+ if (idx == 0)
+ return column.forget();
+
+ idx--;
+ column = GetNextSensibleColumn(column);
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetNextSensibleColumn(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsITreeColumn> nextColumn;
+ aColumn->GetNext(getter_AddRefs(nextColumn));
+
+ while (nextColumn && IsColumnHidden(nextColumn)) {
+ nsCOMPtr<nsITreeColumn> tempColumn;
+ nextColumn->GetNext(getter_AddRefs(tempColumn));
+ nextColumn.swap(tempColumn);
+ }
+
+ return nextColumn.forget();
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetPreviousSensibleColumn(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsITreeColumn> prevColumn;
+ aColumn->GetPrevious(getter_AddRefs(prevColumn));
+
+ while (prevColumn && IsColumnHidden(prevColumn)) {
+ nsCOMPtr<nsITreeColumn> tempColumn;
+ prevColumn->GetPrevious(getter_AddRefs(tempColumn));
+ prevColumn.swap(tempColumn);
+ }
+
+ return prevColumn.forget();
+}
+
+bool
+nsCoreUtils::IsColumnHidden(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsIDOMElement> element;
+ aColumn->GetElement(getter_AddRefs(element));
+ nsCOMPtr<nsIContent> content = do_QueryInterface(element);
+ return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+void
+nsCoreUtils::ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType)
+{
+ nsIPresShell::ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+ aPresShell->ScrollContentIntoView(aContent, vertical, horizontal,
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+bool
+nsCoreUtils::IsWhitespaceString(const nsSubstring& aString)
+{
+ nsSubstring::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);
+}
+
+void
+nsCoreUtils::XBLBindingRole(const nsIContent* aEl, nsAString& aRole)
+{
+ for (const nsXBLBinding* binding = aEl->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ nsIContent* bindingElm = binding->PrototypeBinding()->GetBindingElement();
+ bindingElm->GetAttr(kNameSpaceID_None, nsGkAtoms::role, aRole);
+ if (!aRole.IsEmpty())
+ break;
+ }
+}
diff --git a/accessible/base/nsCoreUtils.h b/accessible/base/nsCoreUtils.h
new file mode 100644
index 000000000..8d561e55b
--- /dev/null
+++ b/accessible/base/nsCoreUtils.h
@@ -0,0 +1,329 @@
+/* -*- 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 "nsIDocument.h" // for GetShell()
+#include "nsIPresShell.h"
+
+#include "nsPoint.h"
+#include "nsTArray.h"
+
+class nsRange;
+class nsIBoxObject;
+class nsIFrame;
+class nsIDocShell;
+class nsITreeColumn;
+class nsITreeBoxObject;
+class nsIWidget;
+
+/**
+ * Core utils.
+ */
+class nsCoreUtils
+{
+public:
+ /**
+ * 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 aTreeBoxObj [in] tree box object
+ * @param aRowIndex [in] row index
+ * @param aColumn [in] column object
+ * @param aPseudoElm [in] pseudo elemenet inside the cell, see
+ * nsITreeBoxObject for available values
+ */
+ static void DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
+ int32_t aRowIndex, nsITreeColumn *aColumn,
+ const nsAString& aPseudoElt = EmptyString());
+
+ /**
+ * 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
+ */
+ static void DispatchMouseEvent(mozilla::EventMessage aMessage,
+ int32_t aX, int32_t aY,
+ nsIContent *aContent, nsIFrame *aFrame,
+ nsIPresShell *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
+ */
+ static void DispatchTouchEvent(mozilla::EventMessage aMessage,
+ int32_t aX, int32_t aY,
+ nsIContent* aContent, nsIFrame* aFrame,
+ nsIPresShell* 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
+ */
+ 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.
+ */
+ static nsresult ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::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,
+ nsIPresShell::ScrollAxis *aVertical,
+ nsIPresShell::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(nsIDocument *aDocument);
+
+ /**
+ * Return true if the given document is content document (not chrome).
+ */
+ static bool IsContentDocument(nsIDocument *aDocument);
+
+ /**
+ * Return true if the given document node is for tab document accessible.
+ */
+ static bool IsTabDocument(nsIDocument* aDocumentNode);
+
+ /**
+ * Return true if the given document is an error page.
+ */
+ static bool IsErrorPage(nsIDocument *aDocument);
+
+ /**
+ * Return presShell for the document containing the given DOM node.
+ */
+ static nsIPresShell *GetPresShellFor(nsINode *aNode)
+ {
+ return aNode->OwnerDoc()->GetShell();
+ }
+
+ /**
+ * 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, nsIAtom *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 box object for XUL treechildren element by tree box object.
+ */
+ static already_AddRefed<nsIBoxObject>
+ GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj);
+
+ /**
+ * Return tree box object from any levels DOMNode under the XUL tree.
+ */
+ static already_AddRefed<nsITreeBoxObject>
+ GetTreeBoxObject(nsIContent* aContent);
+
+ /**
+ * Return first sensible column for the given tree box object.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetFirstSensibleColumn(nsITreeBoxObject *aTree);
+
+ /**
+ * Return sensible columns count for the given tree box object.
+ */
+ static uint32_t GetSensibleColumnCount(nsITreeBoxObject *aTree);
+
+ /**
+ * Return sensible column at the given index for the given tree box object.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex);
+
+ /**
+ * Return next sensible column for the given column.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetNextSensibleColumn(nsITreeColumn *aColumn);
+
+ /**
+ * Return previous sensible column for the given column.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetPreviousSensibleColumn(nsITreeColumn *aColumn);
+
+ /**
+ * Return true if the given column is hidden (i.e. not sensible).
+ */
+ static bool IsColumnHidden(nsITreeColumn *aColumn);
+
+ /**
+ * Scroll content into view.
+ */
+ static void ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType);
+
+ /**
+ * Return true if the given node is table header element.
+ */
+ static bool IsHTMLTableHeader(nsIContent *aContent)
+ {
+ return aContent->NodeInfo()->Equals(nsGkAtoms::th) ||
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope);
+ }
+
+ /**
+ * 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 nsSubstring& 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);
+
+ /**
+ * Return a role attribute on XBL bindings of the element.
+ */
+ static void XBLBindingRole(const nsIContent* aEl, nsAString& aRole);
+};
+
+#endif
diff --git a/accessible/base/nsEventShell.cpp b/accessible/base/nsEventShell.cpp
new file mode 100644
index 000000000..e070acee5
--- /dev/null
+++ b/accessible/base/nsEventShell.cpp
@@ -0,0 +1,79 @@
+/* -*- 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 "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 ? NS_LITERAL_STRING("true") :
+ NS_LITERAL_STRING("false"));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 000000000..169af6dad
--- /dev/null
+++ b/accessible/base/nsEventShell.h
@@ -0,0 +1,67 @@
+/* -*- 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 000000000..bdf14d097
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.cpp
@@ -0,0 +1,364 @@
+/* -*- 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 "nsIDOMXULLabeledControlEl.h"
+
+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 Accessible* sInitiatorAcc = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsTextEquivUtils. Public.
+
+nsresult
+nsTextEquivUtils::GetNameFromSubtree(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(Accessible* aAccessible,
+ nsIAtom *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(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->IsNodeOfType(nsINode::eTEXT)) {
+ 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::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ aString->Append(text.mString);
+ } else {
+ // If aContent is an object that is display: none, we have no a frame.
+ aContent->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(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) {
+ uint32_t nameRule = GetRoleRule(aAccessible->Role());
+ if (nameRule & eNameFromSubtreeIfReqRule) {
+ 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;
+ nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
+ do_QueryInterface(aContent);
+
+ if (labeledEl) {
+ labeledEl->GetLabel(textEquivalent);
+ } else {
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
+ kNameSpaceID_XUL))
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ textEquivalent);
+
+ if (textEquivalent.IsEmpty())
+ aContent->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, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ return nameRule;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_CRASH("Unknown role.");
+ }
+
+#undef ROLE
+}
diff --git a/accessible/base/nsTextEquivUtils.h b/accessible/base/nsTextEquivUtils.h
new file mode 100644
index 000000000..3c35d0c07
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.h
@@ -0,0 +1,162 @@
+/* -*- 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(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(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(Accessible* aAccessible,
+ nsIAtom *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(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(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);
+};
+
+#endif