diff --git a/accessible/public/Makefile.in b/accessible/public/Makefile.in index bfa0dde4aa08..b25d0eae0c39 100644 --- a/accessible/public/Makefile.in +++ b/accessible/public/Makefile.in @@ -62,10 +62,12 @@ XPIDLSRCS = \ nsIAccessibleProvider.idl \ nsIAccessibleSelectable.idl \ nsIAccessNode.idl \ + nsIAccessibleCursorable.idl \ nsIAccessibleEvent.idl \ nsIAccessibleEditableText.idl \ nsIAccessibleHyperLink.idl \ nsIAccessibleHyperText.idl \ + nsIAccessiblePivot.idl \ nsIAccessibleTable.idl \ nsIAccessibleText.idl \ nsIAccessibleValue.idl \ diff --git a/accessible/public/nsIAccessibleCursorable.idl b/accessible/public/nsIAccessibleCursorable.idl new file mode 100644 index 000000000000..7f1f34c1a6c7 --- /dev/null +++ b/accessible/public/nsIAccessibleCursorable.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2012 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Eitan Isaacson (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +interface nsIAccessiblePivot; + +/** + * An interface implemented by an accessible object that has an associated + * virtual cursor. Typically, a top-level application or content document. + * A virtual cursor is an implementation of nsIAccessiblePivot that provides an + * exclusive spot in the cursorable's subtree, this could be used to create a + * pseudo-focus or caret browsing experience that is centered around the + * accessibility API. + */ +[scriptable, uuid(5452dea5-d235-496f-8757-3ca016ff49ff)] +interface nsIAccessibleCursorable : nsISupports +{ + /** + * The virtual cursor pivot this object manages. + */ + readonly attribute nsIAccessiblePivot virtualCursor; +}; diff --git a/accessible/public/nsIAccessibleEvent.idl b/accessible/public/nsIAccessibleEvent.idl index 7f609f8ae122..336747ba45e4 100644 --- a/accessible/public/nsIAccessibleEvent.idl +++ b/accessible/public/nsIAccessibleEvent.idl @@ -441,10 +441,15 @@ interface nsIAccessibleEvent : nsISupports */ const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055; + /** + * A cursorable's virtual cursor has changed. + */ + const unsigned long EVENT_VIRTUALCURSOR_CHANGED = 0x0056; + /** * Help make sure event map does not get out-of-line. */ - const unsigned long EVENT_LAST_ENTRY = 0x0056; + const unsigned long EVENT_LAST_ENTRY = 0x0057; /** * The type of event, based on the enumerated event values diff --git a/accessible/public/nsIAccessiblePivot.idl b/accessible/public/nsIAccessiblePivot.idl new file mode 100644 index 000000000000..e0d9612d8cbb --- /dev/null +++ b/accessible/public/nsIAccessiblePivot.idl @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Eitan Isaacson (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +typedef short TextBoundaryType; + +interface nsIAccessible; +interface nsIAccessibleText; +interface nsIAccessibleTraversalRule; +interface nsIAccessiblePivotObserver; + +/** + * The pivot interface encapsulates a reference to a single place in an accessible + * subtree. The pivot is a point or a range in the accessible tree. This interface + * provides traversal methods to move the pivot to next/prev state that complies + * to a given rule. + */ +[scriptable, uuid(689058ae-e301-444f-acb0-b5c2b189f350)] +interface nsIAccessiblePivot : nsISupports +{ + const TextBoundaryType CHAR_BOUNDARY = 0; + const TextBoundaryType WORD_BOUNDARY = 1; + const TextBoundaryType LINE_BOUNDARY = 2; + const TextBoundaryType ATTRIBUTE_RANGE_BOUNDARY = 3; + + /** + * The accessible the pivot is currently pointed at. + */ + attribute nsIAccessible position; + + /** + * The root of the subtree in which the pivot traverses. + */ + readonly attribute nsIAccessible root; + + /** + * The start offset of the text range the pivot points at, otherwise -1. + */ + readonly attribute long startOffset; + + /** + * The end offset of the text range the pivot points at, otherwise -1. + */ + readonly attribute long endOffset; + + /** + * Set the pivot's text range in a text accessible. + * + * @param aTextAccessible [in] the text accessible that contains the desired + * range. + * @param aStartOffset [in] the start offset to set. + * @param aEndOffset [in] the end offset to set. + * @throws NS_ERROR_INVALID_ARG when the offset exceeds the accessible's + * character count. + */ + void setTextRange(in nsIAccessibleText aTextAccessible, + in long aStartOffset, in long aEndOffset); + + /** + * Move pivot to next object complying to given traversal rule. + * + * @param aRule [in] traversal rule to use. + * @return true on success, false if there are no new nodes to traverse to. + */ + boolean moveNext(in nsIAccessibleTraversalRule aRule); + + /** + * Move pivot to previous object complying to given traversal rule. + * + * @param aRule [in] traversal rule to use. + * @return true on success, false if there are no new nodes to traverse to. + */ + boolean movePrevious(in nsIAccessibleTraversalRule aRule); + + /** + * Move pivot to first object in subtree complying to given traversal rule. + * + * @param aRule [in] traversal rule to use. + * @return true on success, false if there are no new nodes to traverse to. + */ + boolean moveFirst(in nsIAccessibleTraversalRule aRule); + + /** + * Move pivot to last object in subtree complying to given traversal rule. + * + * @param aRule [in] traversal rule to use. + * @return true on success, false if there are no new nodes to traverse to. + */ + boolean moveLast(in nsIAccessibleTraversalRule aRule); + + /** + * Move pivot to next text range. + * + * @param aBoundary [in] type of boundary for next text range, character, word, + * etc. + * @return true on success, false if there are is no more text. + */ + boolean moveNextByText(in TextBoundaryType aBoundary); + + /** + * Move pivot to previous text range. + * + * @param aBoundary [in] type of boundary for previous text range, character, + * word, etc. + * @return true on success, false if there are is no more text. + */ + boolean movePreviousByText(in TextBoundaryType aBoundary); + + /** + * Add an observer for pivot changes. + * + * @param aObserver [in] the observer object to be notified of pivot changes. + */ + void addObserver(in nsIAccessiblePivotObserver aObserver); + + /** + * Remove an observer for pivot changes. + * + * @param aObserver [in] the observer object to remove from being notified. + */ + void removeObserver(in nsIAccessiblePivotObserver aObserver); +}; + +/** + * An observer interface for pivot changes. + */ +[scriptable, uuid(b6508c5e-c081-467d-835c-613eedf9ee9b)] +interface nsIAccessiblePivotObserver : nsISupports +{ + /** + * Called when the pivot changes. + * + * @param aPivot [in] the pivot that has changed. + * @param aOldAccessible [in] the old pivot position before the change, or null. + * @param aOldStart [in] the old start offset, or -1. + * @param aOldEnd [in] the old end offset, or -1. + */ + void onPivotChanged(in nsIAccessiblePivot aPivot, + in nsIAccessible aOldAccessible, + in long aOldStart, in long aOldEnd); +}; + +[scriptable, uuid(307d98b6-dba9-49cf-ba17-ef8b053044eb)] +interface nsIAccessibleTraversalRule : nsISupports +{ + /* Ignore this accessible object */ + const unsigned short FILTER_IGNORE = 0x0; + /* Accept this accessible object */ + const unsigned short FILTER_MATCH = 0x1; + /* Don't traverse accessibles children */ + const unsigned short FILTER_IGNORE_SUBTREE = 0x2; + + /* Pre-filters */ + const unsigned long PREFILTER_INVISIBLE = 0x00000001; + const unsigned long PREFILTER_OFFSCREEN = 0x00000002; + const unsigned long PREFILTER_NOT_FOCUSABLE = 0x00000004; + + /** + * Pre-filter bitfield to filter out obviously ignorable nodes and lighten + * the load on match(). + */ + readonly attribute unsigned long preFilter; + + /** + * Retrieve a list of roles that the traversal rule should test for. Any node + * with a role not in this list will automatically be ignored. An empty list + * will match all roles. It should be assumed that this method is called once + * at the start of a traversal, so changing the method's return result after + * that would have no affect. + * + * @param aRoles [out] an array of the roles to match. + * @param aCount [out] the length of the array. + */ + void getMatchRoles([array, size_is(aCount)]out unsigned long aRoles, + [retval]out unsigned long aCount); + + /** + * Determines if a given accessible is to be accepted in our traversal rule + * + * @param aAccessible [in] accessible to examine. + * @return a bitfield of FILTER_MATCH and FILTER_IGNORE_SUBTREE. + */ + unsigned short match(in nsIAccessible aAccessible); +}; diff --git a/accessible/public/nsIAccessibleRetrieval.idl b/accessible/public/nsIAccessibleRetrieval.idl index 1a13678ba744..e433b20cc66f 100644 --- a/accessible/public/nsIAccessibleRetrieval.idl +++ b/accessible/public/nsIAccessibleRetrieval.idl @@ -45,7 +45,7 @@ interface nsIPresShell; interface nsIDOMWindow; interface nsIAccessNode; interface nsIDOMDOMStringList; - +interface nsIAccessiblePivot; /** * An interface for in-process accessibility clients @@ -112,6 +112,14 @@ interface nsIAccessibleRetrieval : nsISupports * @return cached accessible for the given DOM node if any */ nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode); + + /** + * Create a new pivot for tracking a position and traversing a subtree. + * + * @param aRoot [in] the accessible root for the pivot + * @return a new pivot + */ + nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot); }; diff --git a/accessible/src/base/Makefile.in b/accessible/src/base/Makefile.in index 0af0dd342311..de7a077fff08 100644 --- a/accessible/src/base/Makefile.in +++ b/accessible/src/base/Makefile.in @@ -65,6 +65,7 @@ CPPSRCS = \ nsAccUtils.cpp \ nsAccessibilityService.cpp \ nsAccessible.cpp \ + nsAccessiblePivot.cpp \ nsAccTreeWalker.cpp \ nsBaseWidgetAccessible.cpp \ nsEventShell.cpp \ diff --git a/accessible/src/base/nsAccessibilityService.cpp b/accessible/src/base/nsAccessibilityService.cpp index 2598db9788bf..7c14df58b366 100644 --- a/accessible/src/base/nsAccessibilityService.cpp +++ b/accessible/src/base/nsAccessibilityService.cpp @@ -40,6 +40,7 @@ // NOTE: alphabetically ordered #include "nsAccessibilityService.h" +#include "nsAccessiblePivot.h" #include "nsCoreUtils.h" #include "nsAccUtils.h" #include "nsApplicationAccessibleWrap.h" @@ -864,6 +865,23 @@ nsAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode, return NS_OK; } +NS_IMETHODIMP +nsAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot, + nsIAccessiblePivot** aPivot) +{ + NS_ENSURE_ARG_POINTER(aPivot); + NS_ENSURE_ARG(aRoot); + *aPivot = nsnull; + + nsRefPtr accessibleRoot(do_QueryObject(aRoot)); + NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG); + + nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot); + NS_ADDREF(*aPivot = pivot); + + return NS_OK; +} + // nsIAccesibilityService nsAccessible* nsAccessibilityService::GetAccessibleInShell(nsINode* aNode, diff --git a/accessible/src/base/nsAccessibilityService.h b/accessible/src/base/nsAccessibilityService.h index 6370e74d6fae..50016ae78599 100644 --- a/accessible/src/base/nsAccessibilityService.h +++ b/accessible/src/base/nsAccessibilityService.h @@ -534,6 +534,7 @@ static const char kEventTypeNames[][40] = { "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 }; /** diff --git a/accessible/src/base/nsAccessiblePivot.cpp b/accessible/src/base/nsAccessiblePivot.cpp new file mode 100644 index 000000000000..6c6688577764 --- /dev/null +++ b/accessible/src/base/nsAccessiblePivot.cpp @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Eitan Isaacson (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsAccessiblePivot.h" + +#include "nsAccessible.h" +#include "nsAccUtils.h" +#include "nsHyperTextAccessible.h" +#include "States.h" + +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsISupportsPrimitives.h" + +using namespace mozilla::a11y; + + +/** + * An object that stores a given traversal rule during + */ +class RuleCache +{ +public: + RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule), + mAcceptRoles(nsnull) { } + ~RuleCache () { + if (mAcceptRoles) + nsMemory::Free(mAcceptRoles); + } + + nsresult ApplyFilter(nsAccessible* aAccessible, PRUint16* aResult); + +private: + nsCOMPtr mRule; + PRUint32* mAcceptRoles; + PRUint32 mAcceptRolesLength; + PRUint32 mPreFilter; +}; + +//////////////////////////////////////////////////////////////////////////////// +// nsAccessiblePivot + +nsAccessiblePivot::nsAccessiblePivot(nsAccessible* aRoot) : + mRoot(aRoot), mPosition(nsnull), + mStartOffset(-1), mEndOffset(-1) +{ + NS_ASSERTION(aRoot, "A root accessible is required"); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsISupports + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessiblePivot) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccessiblePivot) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRoot, nsIAccessible) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mPosition, nsIAccessible) + PRUint32 i, length = tmp->mObservers.Length(); \ + for (i = 0; i < length; ++i) { + cb.NoteXPCOMChild(tmp->mObservers.ElementAt(i).get()); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccessiblePivot) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPosition) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mObservers) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot) + NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot) + NS_INTERFACE_MAP_ENTRY(nsAccessiblePivot) + 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 = mRoot); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::GetPosition(nsIAccessible** aPosition) +{ + NS_ENSURE_ARG_POINTER(aPosition); + + NS_IF_ADDREF(*aPosition = mPosition); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::SetPosition(nsIAccessible* aPosition) +{ + nsRefPtr secondPosition; + + if (aPosition) { + secondPosition = do_QueryObject(aPosition); + if (!secondPosition || !IsRootDescendant(secondPosition)) + return NS_ERROR_INVALID_ARG; + } + + // Swap old position with new position, saves us an AddRef/Release. + mPosition.swap(secondPosition); + PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset; + mStartOffset = mEndOffset = -1; + NotifyPivotChanged(secondPosition, oldStart, oldEnd); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::GetStartOffset(PRInt32* aStartOffset) +{ + NS_ENSURE_ARG_POINTER(aStartOffset); + + *aStartOffset = mStartOffset; + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::GetEndOffset(PRInt32* aEndOffset) +{ + NS_ENSURE_ARG_POINTER(aEndOffset); + + *aEndOffset = mEndOffset; + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible, + PRInt32 aStartOffset, PRInt32 aEndOffset) +{ + 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); + + nsRefPtr newPosition = do_QueryObject(aTextAccessible); + if (!newPosition || !IsRootDescendant(newPosition)) + return NS_ERROR_INVALID_ARG; + + // Make sure the given offsets don't exceed the character count. + PRInt32 charCount = newPosition->CharacterCount(); + + if (aEndOffset > charCount) + return NS_ERROR_FAILURE; + + PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset; + mStartOffset = aStartOffset; + mEndOffset = aEndOffset; + + nsRefPtr oldPosition = mPosition.forget(); + mPosition = newPosition.forget(); + + NotifyPivotChanged(oldPosition, oldStart, oldEnd); + + return NS_OK; +} + +// Traversal functions + +NS_IMETHODIMP +nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aRule); + + nsresult rv = NS_OK; + nsAccessible* accessible = SearchForward(mPosition, aRule, false, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = accessible; + if (*aResult) + MovePivotInternal(accessible); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aRule); + + nsresult rv = NS_OK; + nsAccessible* accessible = SearchBackward(mPosition, aRule, false, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = accessible; + if (*aResult) + MovePivotInternal(accessible); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aRule); + nsresult rv = NS_OK; + nsAccessible* accessible = SearchForward(mRoot, aRule, true, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = accessible; + if (*aResult) + MovePivotInternal(accessible); + + return NS_OK; +} + +NS_IMETHODIMP +nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aRule); + + *aResult = false; + nsresult rv = NS_OK; + nsAccessible* lastAccessible = mRoot; + nsAccessible* accessible = nsnull; + + // First got 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); + + *aResult = accessible; + if (*aResult) + MovePivotInternal(accessible); + + return NS_OK; +} + +// TODO: Implement +NS_IMETHODIMP +nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + + *aResult = false; + + return NS_ERROR_NOT_IMPLEMENTED; +} + +// TODO: Implement +NS_IMETHODIMP +nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult) +{ + NS_ENSURE_ARG(aResult); + + *aResult = false; + + return NS_ERROR_NOT_IMPLEMENTED; +} + +// 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::IsRootDescendant(nsAccessible* aAccessible) +{ + nsAccessible* accessible = aAccessible; + do { + if (accessible == mRoot) + return true; + } while ((accessible = accessible->Parent())); + + return false; +} + +void +nsAccessiblePivot::MovePivotInternal(nsAccessible* aPosition) +{ + nsRefPtr oldPosition = mPosition.forget(); + mPosition = aPosition; + PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset; + mStartOffset = mEndOffset = -1; + + NotifyPivotChanged(oldPosition, oldStart, oldEnd); +} + +nsAccessible* +nsAccessiblePivot::SearchBackward(nsAccessible* aAccessible, + nsIAccessibleTraversalRule* aRule, + bool searchCurrent, + nsresult* rv) +{ + *rv = NS_OK; + + // Initial position could be unset, in that case return null. + if (!aAccessible) + return nsnull; + + RuleCache cache(aRule); + nsAccessible* accessible = aAccessible; + + PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; + + if (searchCurrent) { + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) + return accessible; + } + + while (accessible != mRoot) { + nsAccessible* parent = accessible->Parent(); + PRInt32 idxInParent = accessible->IndexInParent(); + while (idxInParent > 0) { + if (!(accessible = parent->GetChildAt(--idxInParent))) + continue; + + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + + nsAccessible* lastChild; + while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && + (lastChild = accessible->LastChild())) { + parent = accessible; + accessible = lastChild; + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + } + + if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) + return accessible; + } + + if (!(accessible = parent)) + break; + + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + + if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) + return accessible; + } + + return nsnull; +} + +nsAccessible* +nsAccessiblePivot::SearchForward(nsAccessible* aAccessible, + nsIAccessibleTraversalRule* aRule, + bool searchCurrent, + nsresult* rv) +{ + *rv = NS_OK; + + // Initial position could be not set, in that case begin search from root. + nsAccessible *accessible = (!aAccessible) ? mRoot.get() : aAccessible; + + RuleCache cache(aRule); + + PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + if (searchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) + return accessible; + + while (true) { + nsAccessible* firstChild = nsnull; + while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && + (firstChild = accessible->FirstChild())) { + accessible = firstChild; + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + + if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) + return accessible; + } + + nsAccessible* sibling = nsnull; + nsAccessible* temp = accessible; + do { + if (temp == mRoot) + break; + + sibling = temp->NextSibling(); + + if (sibling) + break; + } while ((temp = temp->Parent())); + + if (!sibling) + break; + + accessible = sibling; + *rv = cache.ApplyFilter(accessible, &filtered); + NS_ENSURE_SUCCESS(*rv, nsnull); + + if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) + return accessible; + } + + return nsnull; +} + +void +nsAccessiblePivot::NotifyPivotChanged(nsAccessible* aOldPosition, + PRInt32 aOldStart, PRInt32 aOldEnd) +{ + nsTObserverArray >::ForwardIterator iter(mObservers); + while (iter.HasMore()) { + nsIAccessiblePivotObserver* obs = iter.GetNext(); + obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd); + } +} + +nsresult +RuleCache::ApplyFilter(nsAccessible* aAccessible, PRUint16* 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) { + PRUint64 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 (mAcceptRolesLength > 0) { + PRUint32 accessibleRole = aAccessible->Role(); + bool matchesRole = false; + for (PRUint32 idx = 0; idx < mAcceptRolesLength; idx++) { + matchesRole = mAcceptRoles[idx] == accessibleRole; + if (matchesRole) + break; + } + if (!matchesRole) + return NS_OK; + } + + return mRule->Match(aAccessible, aResult); +} diff --git a/accessible/src/base/nsAccessiblePivot.h b/accessible/src/base/nsAccessiblePivot.h new file mode 100644 index 000000000000..2f1e58ec3437 --- /dev/null +++ b/accessible/src/base/nsAccessiblePivot.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Eitan Isaacson (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef _nsAccessiblePivot_H_ +#define _nsAccessiblePivot_H_ + +#include "nsIAccessiblePivot.h" + +#include "nsAutoPtr.h" +#include "nsTObserverArray.h" +#include "nsCycleCollectionParticipant.h" + +class nsAccessible; +class nsIAccessibleTraversalRule; + +/** + * Class represents an accessible pivot. + */ +class nsAccessiblePivot: public nsIAccessiblePivot +{ +public: + nsAccessiblePivot(nsAccessible* 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. + */ + nsAccessible* Position() { return mPosition; } + +private: + nsAccessiblePivot() MOZ_DELETE; + nsAccessiblePivot(const nsAccessiblePivot&) MOZ_DELETE; + void operator = (const nsAccessiblePivot&) MOZ_DELETE; + + /* + * Notify all observers on a pivot change. + */ + void NotifyPivotChanged(nsAccessible* aOldAccessible, + PRInt32 aOldStart, PRInt32 aOldEnd); + + /* + * Check to see that the given accessible is in the pivot's subtree. + */ + bool IsRootDescendant(nsAccessible* aAccessible); + + + /* + * Search in preorder for the first accessible to match the rule. + */ + nsAccessible* SearchForward(nsAccessible* aAccessible, + nsIAccessibleTraversalRule* aRule, + bool searchCurrent, + nsresult* rv); + + /* + * Reverse search in preorder for the first accessible to match the rule. + */ + nsAccessible* SearchBackward(nsAccessible* aAccessible, + nsIAccessibleTraversalRule* aRule, + bool searchCurrent, + nsresult* rv); + + /* + * Update the pivot, and notify observers. + */ + void MovePivotInternal(nsAccessible* aPosition); + + /* + * The root accessible. + */ + nsRefPtr mRoot; + + /* + * The current pivot position. + */ + nsRefPtr mPosition; + + /* + * The text start offset ofthe pivot. + */ + PRInt32 mStartOffset; + + /* + * The text end offset ofthe pivot. + */ + PRInt32 mEndOffset; + + /* + * The list of pivot-changed observers. + */ + nsTObserverArray > mObservers; +}; + +#endif diff --git a/accessible/src/base/nsDocAccessible.cpp b/accessible/src/base/nsDocAccessible.cpp index ea950f4cb352..1701390079e5 100644 --- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -39,6 +39,7 @@ #include "AccIterator.h" #include "nsAccCache.h" #include "nsAccessibilityService.h" +#include "nsAccessiblePivot.h" #include "nsAccTreeWalker.h" #include "nsAccUtils.h" #include "nsRootAccessible.h" @@ -105,7 +106,8 @@ nsDocAccessible:: nsIWeakReference *aShell) : nsHyperTextAccessibleWrap(aRootContent, aShell), mDocument(aDocument), mScrollPositionChangedTicks(0), - mLoadState(eTreeConstructionPending), mLoadEventType(0) + mLoadState(eTreeConstructionPending), mLoadEventType(0), + mVirtualCursor(nsnull) { mFlags |= eDocAccessible; @@ -125,6 +127,10 @@ nsDocAccessible:: // nsAccDocManager creates document accessible when scrollable frame is // available already, it should be safe time to add scroll listener. AddScrollListener(); + + // We provide a virtual cursor if this is a root doc or if it's a tab doc. + mIsCursorable = (!(mDocument->GetParentDocument()) || + nsCoreUtils::IsTabDocument(mDocument)); } nsDocAccessible::~nsDocAccessible() @@ -142,6 +148,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNotificationController, NotificationController) + if (tmp->mVirtualCursor) { + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mVirtualCursor, + nsAccessiblePivot) + } + PRUint32 i, length = tmp->mChildDocuments.Length(); for (i = 0; i < length; ++i) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mChildDocuments[i], @@ -154,6 +165,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNotificationController) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mVirtualCursor) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments) tmp->mDependentIDsHash.Clear(); tmp->mNodeToAccessibleMap.Clear(); @@ -167,7 +179,10 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDocAccessible) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleCursorable, + mIsCursorable) foundInterface = 0; nsresult status; @@ -516,6 +531,27 @@ nsDocAccessible::GetChildDocumentAt(PRUint32 aIndex, return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG; } +// nsIAccessibleVirtualCursor method +NS_IMETHODIMP +nsDocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor) +{ + NS_ENSURE_ARG_POINTER(aVirtualCursor); + *aVirtualCursor = nsnull; + + if (IsDefunct()) + return NS_ERROR_FAILURE; + + NS_ENSURE_TRUE(mIsCursorable, NS_ERROR_NOT_IMPLEMENTED); + + if (!mVirtualCursor) { + mVirtualCursor = new nsAccessiblePivot(this); + mVirtualCursor->AddObserver(this); + } + + NS_ADDREF(*aVirtualCursor = mVirtualCursor); + return NS_OK; +} + // nsIAccessibleHyperText method NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor) { @@ -637,6 +673,11 @@ nsDocAccessible::Shutdown() mChildDocuments.Clear(); + if (mVirtualCursor) { + mVirtualCursor->RemoveObserver(this); + mVirtualCursor = nsnull; + } + mWeakShell = nsnull; // Avoid reentrancy mDependentIDsHash.Clear(); @@ -888,6 +929,20 @@ NS_IMETHODIMP nsDocAccessible::Observe(nsISupports *aSubject, const char *aTopic return NS_OK; } +//////////////////////////////////////////////////////////////////////////////// +// nsIAccessiblePivotObserver + +NS_IMETHODIMP +nsDocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot, + nsIAccessible* aOldAccessible, + PRInt32 aOldStart, PRInt32 aOldEnd) +{ + nsRefPtr event = new AccEvent(nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, this); + nsEventShell::FireEvent(event); + + return NS_OK; +} + //////////////////////////////////////////////////////////////////////////////// // nsIDocumentObserver diff --git a/accessible/src/base/nsDocAccessible.h b/accessible/src/base/nsDocAccessible.h index 19b34f9f6941..a71569fb34d6 100644 --- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -39,7 +39,9 @@ #ifndef _nsDocAccessible_H_ #define _nsDocAccessible_H_ +#include "nsIAccessibleCursorable.h" #include "nsIAccessibleDocument.h" +#include "nsIAccessiblePivot.h" #include "nsEventShell.h" #include "nsHyperTextAccessibleWrap.h" @@ -58,6 +60,7 @@ #include "nsIDocShellTreeNode.h" class nsIScrollableView; +class nsAccessiblePivot; const PRUint32 kDefaultCacheSize = 256; @@ -74,8 +77,10 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap, public nsIDocumentObserver, public nsIObserver, public nsIScrollPositionListener, - public nsSupportsWeakReference -{ + public nsSupportsWeakReference, + public nsIAccessibleCursorable, + public nsIAccessiblePivotObserver +{ NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocAccessible, nsAccessible) @@ -84,6 +89,10 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap, NS_DECL_NSIOBSERVER + NS_DECL_NSIACCESSIBLECURSORABLE + + NS_DECL_NSIACCESSIBLEPIVOTOBSERVER + public: using nsAccessible::GetParent; @@ -595,6 +604,16 @@ protected: nsTArray > mChildDocuments; + /** + * Whether we support nsIAccessibleCursorable, used when querying the interface. + */ + bool mIsCursorable; + + /** + * The virtual cursor of the document when it supports nsIAccessibleCursorable. + */ + nsRefPtr mVirtualCursor; + /** * A storage class for pairing content with one of its relation attributes. */ diff --git a/accessible/src/msaa/nsEventMap.h b/accessible/src/msaa/nsEventMap.h index d11028973643..199665d5d987 100644 --- a/accessible/src/msaa/nsEventMap.h +++ b/accessible/src/msaa/nsEventMap.h @@ -131,6 +131,7 @@ static const PRUint32 gWinEventMap[] = { IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED + kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED kEVENT_LAST_ENTRY // nsIAccessibleEvent::EVENT_LAST_ENTRY }; diff --git a/accessible/tests/mochitest/Makefile.in b/accessible/tests/mochitest/Makefile.in index e90e1e108f3b..2f2fe1d89534 100644 --- a/accessible/tests/mochitest/Makefile.in +++ b/accessible/tests/mochitest/Makefile.in @@ -52,6 +52,7 @@ DIRS = \ hyperlink \ hypertext \ name \ + pivot \ relations \ selectable \ states \ @@ -81,6 +82,7 @@ _TEST_FILES =\ grid.js \ layout.js \ name.js \ + pivot.js \ relations.js \ role.js \ selectable.js \ diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index b40a6529053a..7586197f31e3 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -30,10 +30,13 @@ const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableTex const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink; const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText; +const nsIAccessibleCursorable = Components.interfaces.nsIAccessibleCursorable; const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage; +const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot; const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable; const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable; const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell; +const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule; const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue; const nsIObserverService = Components.interfaces.nsIObserverService; diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js index 7186b111f0bd..0f7d1f89e9e2 100644 --- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -27,6 +27,7 @@ const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED; const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED; const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE; +const EVENT_VIRTUALCURSOR_CHANGED = nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED; //////////////////////////////////////////////////////////////////////////////// // General diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js new file mode 100644 index 000000000000..ba38cdf07cc7 --- /dev/null +++ b/accessible/tests/mochitest/pivot.js @@ -0,0 +1,217 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +//////////////////////////////////////////////////////////////////////////////// +// Constants + +const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE; +const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH; +const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE; +const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE; + +//////////////////////////////////////////////////////////////////////////////// +// Traversal rules + +/** + * Rule object to traverse all focusable nodes and text nodes. + */ +var HeadersTraversalRule = +{ + getMatchRoles: function(aRules) + { + aRules.value = [ROLE_HEADING]; + return aRules.value.length; + }, + + preFilter: PREFILTER_INVISIBLE, + + match: function(aAccessible) + { + return FILTER_MATCH; + }, + + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) +} + +/** + * Traversal rule for all focusable nodes or leafs. + */ +var ObjectTraversalRule = +{ + getMatchRoles: function(aRules) + { + aRules.value = []; + return 0; + }, + + preFilter: PREFILTER_INVISIBLE, + + match: function(aAccessible) + { + var rv = FILTER_IGNORE; + var role = aAccessible.role; + if (hasState(aAccessible, STATE_FOCUSABLE) && + (role != ROLE_DOCUMENT && role != ROLE_INTERNAL_FRAME)) + rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH; + else if (aAccessible.childCount == 0 && + role != ROLE_STATICTEXT && aAccessible.name.trim()) + rv = FILTER_MATCH; + + return rv; + }, + + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) +}; + +//////////////////////////////////////////////////////////////////////////////// +// Virtual state invokers and checkers + +/** + * A checker for virtual cursor changed events. + */ +function virtualCursorChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets) +{ + this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc); + + this.check = function virtualCursorChangedChecker_check(aEvent) + { + var position = aDocAcc.virtualCursor.position; + position.QueryInterface(nsIAccessNode); + + var idMatches = position.DOMNode.id == aIdOrNameOrAcc; + var nameMatches = position.name == aIdOrNameOrAcc; + var accMatches = position == aIdOrNameOrAcc; + + SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches", + "expecting " + aIdOrNameOrAcc + ", got '" + + prettyName(position)); + + if (aTextOffsets) { + SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0], + "wrong start offset"); + SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1], + "wrong end offset"); + } + }; +} + +/** + * Set a text range in the pivot and wait for virtual cursor change event. + * + * @param aDocAcc document that manages the virtual cursor + * @param aTextAccessible accessible to set to virtual cursor's position + * @param aTextOffsets start and end offsets of text range to set in virtual + * cursor + */ +function setVirtualCursorRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) +{ + this.invoke = function virtualCursorChangedInvoker_invoke() + { + SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets); + aDocAcc.virtualCursor.setTextRange(aTextAccessible, + aTextOffsets[0], + aTextOffsets[1]); + }; + + this.getID = function setVirtualCursorRangeInvoker_getID() + { + return "Set offset in " + prettyName(aTextAccessible) + + " to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")"; + } + + this.eventSeq = [ + new virtualCursorChangedChecker(aDocAcc, aTextAccessible, aTextOffsets) + ]; +} + +/** + * Move the pivot and wait for virtual cursor change event. + * + * @param aDocAcc document that manages the virtual cursor + * @param aPivotMoveMethod method to test (ie. "moveNext", "moveFirst", etc.) + * @param aRule traversal rule object + * @param aIdOrNameOrAcc id, accessivle or accessible name to expect virtual + * cursor to land on after performing move method. + */ +function setVirtualCursorPosInvoker(aDocAcc, aPivotMoveMethod, aRule, + aIdOrNameOrAcc) +{ + this.invoke = function virtualCursorChangedInvoker_invoke() + { + var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule); + SimpleTest.ok((aIdOrNameOrAcc && moved) || (!aIdOrNameOrAcc && !moved), + "moved pivot"); + }; + + this.getID = function setVirtualCursorPosInvoker_getID() + { + return "Do " + (aIdOrNameOrAcc ? "" : "no-op ") + aPivotMoveMethod; + } + + if (aIdOrNameOrAcc) { + this.eventSeq = [ new virtualCursorChangedChecker(aDocAcc, aIdOrNameOrAcc) ]; + } else { + this.eventSeq = []; + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc) + ]; + } +} + +/** + * Add invokers to a queue to test a rule and an expected sequence of element ids + * or accessible names for that rule in the given document. + * + * @param aQueue event queue in which to push invoker sequence. + * @param aDocAcc the managing document of the virtual cursor we are testing + * @param aRule the traversal rule to use in the invokers + * @param aSequence a sequence of accessible names or elemnt ids to expect with + * the given rule in the given document + */ +function queueTraversalSequence(aQueue, aDocAcc, aRule, aSequence) +{ + aDocAcc.virtualCursor.position = null; + + for (var i = 0; i < aSequence.length; i++) { + var invoker = new setVirtualCursorPosInvoker(aDocAcc, "moveNext", + aRule, aSequence[i]); + aQueue.push(invoker); + } + + // No further more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "moveNext", aRule, null)); + + for (var i = aSequence.length-2; i >= 0; i--) { + var invoker = new setVirtualCursorPosInvoker(aDocAcc, "movePrevious", + aRule, aSequence[i]) + aQueue.push(invoker); + } + + // No previous more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "movePrevious", aRule, null)); + + aQueue.push(new setVirtualCursorPosInvoker( + aDocAcc, "moveLast", aRule, aSequence[aSequence.length - 1])); + + // No further more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "moveNext", aRule, null)); + + aQueue.push(new setVirtualCursorPosInvoker( + aDocAcc, "moveFirst", aRule, aSequence[0])); + + // No previous more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVirtualCursorPosInvoker(aDocAcc, "movePrevious", aRule, null)); +} + +/** + * A debug utility for writing proper sequences for queueTraversalSequence. + */ +function dumpTraversalSequence(aPivot, aRule) +{ + var sequence = [] + if (aPivot.moveFirst(aRule)) { + do { + sequence.push("'" + prettyName(aPivot.position) + "'"); + } while (aPivot.moveNext(aRule)) + } + SimpleTest.info("\n[" + sequence.join(", ") + "]\n"); +} \ No newline at end of file diff --git a/accessible/tests/mochitest/pivot/Makefile.in b/accessible/tests/mochitest/pivot/Makefile.in new file mode 100644 index 000000000000..53d376cb601b --- /dev/null +++ b/accessible/tests/mochitest/pivot/Makefile.in @@ -0,0 +1,54 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Eitan Isaacson (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = accessible/pivot + +include $(DEPTH)/config/autoconf.mk +include $(topsrcdir)/config/rules.mk + +_TEST_FILES = \ + doc_virtualcursor.html \ + test_virtualcursor.html \ + $(NULL) + +libs:: $(_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir) diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor.html b/accessible/tests/mochitest/pivot/doc_virtualcursor.html new file mode 100644 index 000000000000..2ec42d1f60da --- /dev/null +++ b/accessible/tests/mochitest/pivot/doc_virtualcursor.html @@ -0,0 +1,26 @@ + + + + Pivot test document + + + +

Main Title

+

First Section Title

+

+ Lorem ipsum dolor sit amet. Integer vitae urna + leo, id semper nulla. +

+

Second Section Title

+

+ Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.

+ +

+ Link 1 + Link 2 + Link 3 +

+ + diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html new file mode 100644 index 000000000000..596cce160e94 --- /dev/null +++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html @@ -0,0 +1,95 @@ + + + + Tests pivot functionality in virtual cursors + + + + + + + + + + + + + + + + + Mozilla Bug 698823 +

+ +
+  
+ +