/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Pivot.h" #include "AccIterator.h" #include "LocalAccessible.h" #include "RemoteAccessible.h" #include "nsAccUtils.h" #include "nsIAccessiblePivot.h" #include "mozilla/a11y/Accessible.h" using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // Pivot //////////////////////////////////////////////////////////////////////////////// Pivot::Pivot(Accessible* aRoot) : mRoot(aRoot) { MOZ_COUNT_CTOR(Pivot); } Pivot::~Pivot() { MOZ_COUNT_DTOR(Pivot); } Accessible* Pivot::AdjustStartPosition(Accessible* aAnchor, PivotRule& aRule, uint16_t* aFilterResult) { Accessible* matched = aAnchor; *aFilterResult = aRule.Match(aAnchor); if (aAnchor && aAnchor != mRoot) { for (Accessible* temp = aAnchor->Parent(); temp && temp != mRoot; temp = temp->Parent()) { uint16_t filtered = aRule.Match(temp); if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) { *aFilterResult = filtered; matched = temp; } } } return matched; } Accessible* Pivot::SearchBackward(Accessible* aAnchor, PivotRule& aRule, bool aSearchCurrent) { // Initial position could be unset, in that case return null. if (!aAnchor) { return nullptr; } uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; Accessible* acc = AdjustStartPosition(aAnchor, aRule, &filtered); if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) { return acc; } while (acc && acc != mRoot) { Accessible* parent = acc->Parent(); #if defined(ANDROID) MOZ_ASSERT( acc->IsLocal() || (acc->IsRemote() && parent->IsRemote()), "Pivot::SearchBackward climbed out of remote subtree in Android!"); #endif int32_t idxInParent = acc->IndexInParent(); while (idxInParent > 0 && parent) { acc = parent->ChildAt(--idxInParent); if (!acc) { continue; } filtered = aRule.Match(acc); Accessible* lastChild = acc->LastChild(); while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && lastChild) { parent = acc; acc = lastChild; idxInParent = acc->IndexInParent(); filtered = aRule.Match(acc); lastChild = acc->LastChild(); } if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { return acc; } } acc = parent; if (!acc) { break; } filtered = aRule.Match(acc); if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { return acc; } } return nullptr; } Accessible* Pivot::SearchForward(Accessible* aAnchor, PivotRule& aRule, bool aSearchCurrent) { // Initial position could be not set, in that case begin search from root. Accessible* acc = aAnchor ? aAnchor : mRoot; uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; acc = AdjustStartPosition(acc, aRule, &filtered); if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) { return acc; } while (acc) { Accessible* firstChild = acc->FirstChild(); while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && firstChild) { acc = firstChild; filtered = aRule.Match(acc); if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { return acc; } firstChild = acc->FirstChild(); } Accessible* sibling = nullptr; Accessible* temp = acc; do { if (temp == mRoot) { break; } sibling = temp->NextSibling(); if (sibling) { break; } temp = temp->Parent(); #if defined(ANDROID) MOZ_ASSERT( acc->IsLocal() || (acc->IsRemote() && temp->IsRemote()), "Pivot::SearchForward climbed out of remote subtree in Android!"); #endif } while (temp); if (!sibling) { break; } acc = sibling; filtered = aRule.Match(acc); if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { return acc; } } return nullptr; } Accessible* Pivot::Next(Accessible* aAnchor, PivotRule& aRule, bool aIncludeStart) { return SearchForward(aAnchor, aRule, aIncludeStart); } Accessible* Pivot::Prev(Accessible* aAnchor, PivotRule& aRule, bool aIncludeStart) { return SearchBackward(aAnchor, aRule, aIncludeStart); } Accessible* Pivot::First(PivotRule& aRule) { return SearchForward(mRoot, aRule, true); } Accessible* Pivot::Last(PivotRule& aRule) { Accessible* lastAcc = mRoot; // First go to the last accessible in pre-order while (lastAcc && lastAcc->HasChildren()) { lastAcc = lastAcc->LastChild(); } // Search backwards from last accessible and find the last occurrence in the // doc return SearchBackward(lastAcc, aRule, true); } Accessible* Pivot::AtPoint(int32_t aX, int32_t aY, PivotRule& aRule) { Accessible* match = nullptr; Accessible* child = mRoot ? mRoot->ChildAtPoint(aX, aY, Accessible::EWhichChildAtPoint::DeepestChild) : nullptr; while (child && (mRoot != child)) { uint16_t filtered = aRule.Match(child); // 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) { LayoutDeviceIntRect childRect = child->IsLocal() ? child->AsLocal()->Bounds() : child->AsRemote()->Bounds(); // Double-check child's bounds since the deepest child may have been out // of bounds. This assures we don't return a false positive. if (childRect.Contains(aX, aY)) { match = child; } } child = child->Parent(); } return match; } // Role Rule PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole) : mRole(aRole), mDirectDescendantsFrom(nullptr) {} PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole, Accessible* aDirectDescendantsFrom) : mRole(aRole), mDirectDescendantsFrom(aDirectDescendantsFrom) {} uint16_t PivotRoleRule::Match(Accessible* aAcc) { uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; if (nsAccUtils::MustPrune(aAcc)) { result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (mDirectDescendantsFrom && (aAcc != mDirectDescendantsFrom)) { // If we've specified mDirectDescendantsFrom, we should ignore // non-direct descendants of from the specified AoP. Because // pivot performs a preorder traversal, the first aAcc // object(s) that don't equal mDirectDescendantsFrom will be // mDirectDescendantsFrom's children. We'll process them, but ignore // their subtrees thereby processing direct descendants of // mDirectDescendantsFrom only. result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (aAcc && aAcc->Role() == mRole) { result |= nsIAccessibleTraversalRule::FILTER_MATCH; } return result; } // State Rule PivotStateRule::PivotStateRule(uint64_t aState) : mState(aState) {} uint16_t PivotStateRule::Match(Accessible* aAcc) { uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; if (nsAccUtils::MustPrune(aAcc)) { result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (aAcc && (aAcc->State() & mState)) { result = nsIAccessibleTraversalRule::FILTER_MATCH | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } return result; } // LocalAccInSameDocRule uint16_t LocalAccInSameDocRule::Match(Accessible* aAcc) { LocalAccessible* acc = aAcc ? aAcc->AsLocal() : nullptr; if (!acc) { return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (acc->IsOuterDoc()) { return nsIAccessibleTraversalRule::FILTER_MATCH | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } return nsIAccessibleTraversalRule::FILTER_MATCH; } // Radio Button Name Rule PivotRadioNameRule::PivotRadioNameRule(const nsString& aName) : mName(aName) {} uint16_t PivotRadioNameRule::Match(Accessible* aAcc) { uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; RemoteAccessible* remote = aAcc->AsRemote(); if (!remote) { // We need the cache to be able to fetch the name attribute below. return result; } if (nsAccUtils::MustPrune(aAcc) || aAcc->IsOuterDoc()) { result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (remote->IsHTMLRadioButton()) { nsString currName = remote->GetCachedHTMLNameAttribute(); if (!currName.IsEmpty() && mName.Equals(currName)) { result |= nsIAccessibleTraversalRule::FILTER_MATCH; } } return result; } // MustPruneSameDocRule uint16_t MustPruneSameDocRule::Match(Accessible* aAcc) { if (!aAcc) { return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } if (nsAccUtils::MustPrune(aAcc) || aAcc->IsOuterDoc()) { return nsIAccessibleTraversalRule::FILTER_MATCH | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; } return nsIAccessibleTraversalRule::FILTER_MATCH; }