forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			292 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "Role.h"
 | |
| #include "LocalAccessible.h"
 | |
| #include "LocalAccessible-inl.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) {}
 | |
| 
 | |
| TraversalRule::TraversalRule(int32_t aGranularity)
 | |
|     : mGranularity(aGranularity) {}
 | |
| 
 | |
| uint16_t TraversalRule::Match(const AccessibleOrProxy& aAccOrProxy) {
 | |
|   MOZ_ASSERT(aAccOrProxy.IsAccessible(),
 | |
|              "Should only receive accessibles when processing on android.");
 | |
|   LocalAccessible* aAccessible = aAccOrProxy.AsAccessible();
 | |
|   uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
 | |
| 
 | |
|   if (nsAccUtils::MustPrune(aAccessible)) {
 | |
|     result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
 | |
|   }
 | |
| 
 | |
|   uint64_t state = aAccessible->State();
 | |
| 
 | |
|   if ((state & states::INVISIBLE) != 0) {
 | |
|     return result;
 | |
|   }
 | |
| 
 | |
|   if ((state & states::OPAQUE1) == 0) {
 | |
|     nsIFrame* frame = aAccessible->GetFrame();
 | |
|     if (frame && frame->StyleEffects()->mOpacity == 0.0f) {
 | |
|       return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   switch (mGranularity) {
 | |
|     case java::SessionAccessibility::HTML_GRANULARITY_LINK:
 | |
|       result |= LinkMatch(aAccessible);
 | |
|       break;
 | |
|     case java::SessionAccessibility::HTML_GRANULARITY_CONTROL:
 | |
|       result |= ControlMatch(aAccessible);
 | |
|       break;
 | |
|     case java::SessionAccessibility::HTML_GRANULARITY_SECTION:
 | |
|       result |= SectionMatch(aAccessible);
 | |
|       break;
 | |
|     case java::SessionAccessibility::HTML_GRANULARITY_HEADING:
 | |
|       result |= HeadingMatch(aAccessible);
 | |
|       break;
 | |
|     case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK:
 | |
|       result |= LandmarkMatch(aAccessible);
 | |
|       break;
 | |
|     default:
 | |
|       result |= DefaultMatch(aAccessible);
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| bool TraversalRule::IsSingleLineage(LocalAccessible* aAccessible) {
 | |
|   LocalAccessible* child = aAccessible;
 | |
|   while (child) {
 | |
|     switch (child->ChildCount()) {
 | |
|       case 0:
 | |
|         return true;
 | |
|       case 1:
 | |
|         child = child->LocalFirstChild();
 | |
|         break;
 | |
|       case 2:
 | |
|         if (LocalAccessible* bullet =
 | |
|                 child->LocalParent()->IsHTMLListItem()
 | |
|                     ? child->LocalParent()->AsHTMLListItem()->Bullet()
 | |
|                     : nullptr) {
 | |
|           child = bullet->LocalNextSibling();
 | |
|         } else {
 | |
|           return false;
 | |
|         }
 | |
|         break;
 | |
|       default:
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool TraversalRule::IsListItemBullet(const LocalAccessible* aAccessible) {
 | |
|   LocalAccessible* parent = aAccessible->LocalParent();
 | |
|   return parent && parent->IsHTMLListItem() &&
 | |
|          parent->AsHTMLListItem()->Bullet() == aAccessible;
 | |
| }
 | |
| 
 | |
| bool TraversalRule::IsFlatSubtree(const LocalAccessible* aAccessible) {
 | |
|   for (auto child = aAccessible->LocalFirstChild(); child;
 | |
|        child = child->LocalNextSibling()) {
 | |
|     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 LocalAccessible* aAccessible) {
 | |
|   nsAutoString name;
 | |
|   aAccessible->Name(name);
 | |
|   name.CompressWhitespace();
 | |
|   return !name.IsEmpty();
 | |
| }
 | |
| 
 | |
| uint16_t TraversalRule::LinkMatch(LocalAccessible* 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(LocalAccessible* aAccessible) {
 | |
|   if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) {
 | |
|     return nsIAccessibleTraversalRule::FILTER_MATCH;
 | |
|   }
 | |
| 
 | |
|   return nsIAccessibleTraversalRule::FILTER_IGNORE;
 | |
| }
 | |
| 
 | |
| uint16_t TraversalRule::SectionMatch(LocalAccessible* 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(LocalAccessible* aAccessible) {
 | |
|   if (aAccessible->LandmarkRole()) {
 | |
|     return nsIAccessibleTraversalRule::FILTER_MATCH;
 | |
|   }
 | |
| 
 | |
|   return nsIAccessibleTraversalRule::FILTER_IGNORE;
 | |
| }
 | |
| 
 | |
| uint16_t TraversalRule::ControlMatch(LocalAccessible* 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->NativeState() & 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(LocalAccessible* 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->NativeState() & 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::LISTITEM:
 | |
|       if (IsFlatSubtree(aAccessible) || IsSingleLineage(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;
 | |
| }
 | 
