summaryrefslogtreecommitdiff
path: root/accessible/base/nsAccessiblePivot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/base/nsAccessiblePivot.cpp')
-rw-r--r--accessible/base/nsAccessiblePivot.cpp924
1 files changed, 924 insertions, 0 deletions
diff --git a/accessible/base/nsAccessiblePivot.cpp b/accessible/base/nsAccessiblePivot.cpp
new file mode 100644
index 0000000000..4a6b9f8500
--- /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);
+}