diff options
Diffstat (limited to 'accessible/android/TraversalRule.cpp')
-rw-r--r-- | accessible/android/TraversalRule.cpp | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/accessible/android/TraversalRule.cpp b/accessible/android/TraversalRule.cpp new file mode 100644 index 0000000000..00e97cc164 --- /dev/null +++ b/accessible/android/TraversalRule.cpp @@ -0,0 +1,290 @@ +/* -*- 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 "TraversalRule.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/a11y/Accessible.h" + +#include "Role.h" +#include "HTMLListAccessible.h" +#include "SessionAccessibility.h" +#include "nsAccUtils.h" +#include "nsIAccessiblePivot.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +TraversalRule::TraversalRule() + : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, + true) {} + +TraversalRule::TraversalRule(int32_t aGranularity, bool aIsLocal) + : mGranularity(aGranularity), mIsLocal(aIsLocal) {} + +uint16_t TraversalRule::Match(Accessible* aAcc) { + MOZ_ASSERT(aAcc); + + if (mIsLocal && aAcc->IsRemote()) { + // If we encounter a remote accessible in a local rule, we should + // ignore the subtree because we won't encounter anymore local accessibles + // in it. + return nsIAccessibleTraversalRule::FILTER_IGNORE | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } else if (!mIsLocal && aAcc->IsLocal()) { + // If we encounter a local accessible in a remote rule we are likely + // traversing backwards/upwards, we don't ignore its subtree because it is + // likely the outer doc root of the remote tree. + return nsIAccessibleTraversalRule::FILTER_IGNORE; + } + + uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; + + if (nsAccUtils::MustPrune(aAcc)) { + result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + uint64_t state = aAcc->State(); + + if ((state & states::INVISIBLE) != 0) { + return result; + } + + if (aAcc->Opacity() == 0.0f) { + return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + switch (mGranularity) { + case java::SessionAccessibility::HTML_GRANULARITY_LINK: + result |= LinkMatch(aAcc); + break; + case java::SessionAccessibility::HTML_GRANULARITY_CONTROL: + result |= ControlMatch(aAcc); + break; + case java::SessionAccessibility::HTML_GRANULARITY_SECTION: + result |= SectionMatch(aAcc); + break; + case java::SessionAccessibility::HTML_GRANULARITY_HEADING: + result |= HeadingMatch(aAcc); + break; + case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK: + result |= LandmarkMatch(aAcc); + break; + default: + result |= DefaultMatch(aAcc); + break; + } + + return result; +} + +bool TraversalRule::IsSingleLineage(Accessible* aAccessible) { + Accessible* child = aAccessible; + while (child) { + switch (child->ChildCount()) { + case 0: + return true; + case 1: + child = child->FirstChild(); + break; + case 2: + if (IsListItemBullet(child->FirstChild())) { + child = child->LastChild(); + } else { + return false; + } + break; + default: + return false; + } + } + + return true; +} + +bool TraversalRule::IsListItemBullet(const Accessible* aAccessible) { + return aAccessible->Role() == roles::LISTITEM_MARKER; +} + +bool TraversalRule::IsFlatSubtree(const Accessible* aAccessible) { + for (auto child = aAccessible->FirstChild(); child; + child = child->NextSibling()) { + roles::Role role = child->Role(); + if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) { + continue; + } + + if (child->ChildCount() > 0 || child->ActionCount() > 0) { + return false; + } + } + + return true; +} + +bool TraversalRule::HasName(const Accessible* aAccessible) { + nsAutoString name; + aAccessible->Name(name); + name.CompressWhitespace(); + return !name.IsEmpty(); +} + +uint16_t TraversalRule::LinkMatch(Accessible* aAccessible) { + if (aAccessible->Role() == roles::LINK && + (aAccessible->State() & states::LINKED) != 0) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::HeadingMatch(Accessible* aAccessible) { + if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::SectionMatch(Accessible* aAccessible) { + roles::Role role = aAccessible->Role(); + if (role == roles::HEADING || role == roles::LANDMARK || + aAccessible->LandmarkRole()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::LandmarkMatch(Accessible* aAccessible) { + if (aAccessible->LandmarkRole()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::ControlMatch(Accessible* aAccessible) { + switch (aAccessible->Role()) { + case roles::PUSHBUTTON: + case roles::SPINBUTTON: + case roles::TOGGLE_BUTTON: + case roles::BUTTONDROPDOWN: + case roles::BUTTONDROPDOWNGRID: + case roles::COMBOBOX: + case roles::LISTBOX: + case roles::ENTRY: + case roles::PASSWORD_TEXT: + case roles::PAGETAB: + case roles::RADIOBUTTON: + case roles::RADIO_MENU_ITEM: + case roles::SLIDER: + case roles::CHECKBUTTON: + case roles::CHECK_MENU_ITEM: + case roles::SWITCH: + case roles::MENUITEM: + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + case roles::LINK: + return LinkMatch(aAccessible); + case roles::EDITCOMBOBOX: + if (aAccessible->State() & states::EDITABLE) { + // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is + // editable. If it's a 1.1 combobox, the combobox is just a container; + // we want to stop on the textbox inside it, not the container. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + default: + break; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::DefaultMatch(Accessible* aAccessible) { + switch (aAccessible->Role()) { + case roles::COMBOBOX: + // We don't want to ignore the subtree because this is often + // where the list box hangs out. + return nsIAccessibleTraversalRule::FILTER_MATCH; + case roles::EDITCOMBOBOX: + if (aAccessible->State() & states::EDITABLE) { + // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is + // editable. If it's a 1.1 combobox, the combobox is just a container; + // we want to stop on the textbox inside it. + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::TEXT_LEAF: + case roles::GRAPHIC: + // Nameless text leaves are boring, skip them. + if (HasName(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::STATICTEXT: + // Ignore list bullets + if (!IsListItemBullet(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::HEADER: + case roles::HEADING: + case roles::COLUMNHEADER: + case roles::ROWHEADER: + case roles::STATUSBAR: + if ((aAccessible->ChildCount() > 0 || HasName(aAccessible)) && + (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible))) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::GRID_CELL: + if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::LABEL: + if (IsFlatSubtree(aAccessible)) { + // Match if this is a label with text but no nested controls. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::MENUITEM: + case roles::LINK: + case roles::PAGETAB: + case roles::PUSHBUTTON: + case roles::CHECKBUTTON: + case roles::RADIOBUTTON: + case roles::PROGRESSBAR: + case roles::BUTTONDROPDOWN: + case roles::BUTTONMENU: + case roles::CHECK_MENU_ITEM: + case roles::PASSWORD_TEXT: + case roles::RADIO_MENU_ITEM: + case roles::TOGGLE_BUTTON: + case roles::ENTRY: + case roles::KEY: + case roles::SLIDER: + case roles::SPINBUTTON: + case roles::OPTION: + case roles::SWITCH: + case roles::MATHML_MATH: + // Ignore the subtree, if there is one. So that we don't land on + // the same content that was already presented by its parent. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + default: + break; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} |