summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/pivot.js
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/mochitest/pivot.js')
-rw-r--r--accessible/tests/mochitest/pivot.js551
1 files changed, 551 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js
new file mode 100644
index 000000000..7bb1d81ac
--- /dev/null
+++ b/accessible/tests/mochitest/pivot.js
@@ -0,0 +1,551 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
+const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN;
+const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT;
+const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
+const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
+const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY;
+const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY;
+
+const NS_ERROR_NOT_IN_TREE = 0x80780026;
+const NS_ERROR_INVALID_ARG = 0x80070057;
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT,
+
+ 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 VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod,
+ aIsFromUserInput)
+{
+ this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
+
+ this.match = function VCChangedChecker_match(aEvent)
+ {
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ return false;
+ }
+
+ var expectedReason = VCChangedChecker.methodReasonMap[aPivotMoveMethod] ||
+ nsIAccessiblePivot.REASON_NONE;
+
+ return event.reason == expectedReason;
+ };
+
+ this.check = function VCChangedChecker_check(aEvent)
+ {
+ SimpleTest.info("VCChangedChecker_check");
+
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ SimpleTest.ok(false, "Does not support correct interface: " + e);
+ }
+
+ var position = aDocAcc.virtualCursor.position;
+ var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc;
+ var nameMatches = position && position.name == aIdOrNameOrAcc;
+ var accMatches = position == aIdOrNameOrAcc;
+
+ SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches",
+ "expecting " + aIdOrNameOrAcc + ", got '" +
+ prettyName(position));
+
+ SimpleTest.is(aEvent.isFromUserInput, aIsFromUserInput,
+ "Expected user input is " + aIsFromUserInput + '\n');
+
+ if (aTextOffsets) {
+ SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0],
+ "wrong start offset");
+ SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1],
+ "wrong end offset");
+ }
+
+ var prevPosAndOffset = VCChangedChecker.
+ getPreviousPosAndOffset(aDocAcc.virtualCursor);
+
+ if (prevPosAndOffset) {
+ SimpleTest.is(event.oldAccessible, prevPosAndOffset.position,
+ "previous position does not match");
+ SimpleTest.is(event.oldStartOffset, prevPosAndOffset.startOffset,
+ "previous start offset does not match");
+ SimpleTest.is(event.oldEndOffset, prevPosAndOffset.endOffset,
+ "previous end offset does not match");
+ }
+ };
+}
+
+VCChangedChecker.prevPosAndOffset = {};
+
+VCChangedChecker.storePreviousPosAndOffset =
+ function storePreviousPosAndOffset(aPivot)
+{
+ VCChangedChecker.prevPosAndOffset[aPivot] =
+ {position: aPivot.position,
+ startOffset: aPivot.startOffset,
+ endOffset: aPivot.endOffset};
+};
+
+VCChangedChecker.getPreviousPosAndOffset =
+ function getPreviousPosAndOffset(aPivot)
+{
+ return VCChangedChecker.prevPosAndOffset[aPivot];
+};
+
+VCChangedChecker.methodReasonMap = {
+ 'moveNext': nsIAccessiblePivot.REASON_NEXT,
+ 'movePrevious': nsIAccessiblePivot.REASON_PREV,
+ 'moveFirst': nsIAccessiblePivot.REASON_FIRST,
+ 'moveLast': nsIAccessiblePivot.REASON_LAST,
+ 'setTextRange': nsIAccessiblePivot.REASON_TEXT,
+ 'moveNextByText': nsIAccessiblePivot.REASON_TEXT,
+ 'movePreviousByText': nsIAccessiblePivot.REASON_TEXT,
+ 'moveToPoint': nsIAccessiblePivot.REASON_POINT
+};
+
+/**
+ * Set a text range in the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aTextAccessible [in] accessible to set to virtual cursor's position
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ */
+function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets)
+{
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets);
+ aDocAcc.virtualCursor.setTextRange(aTextAccessible,
+ aTextOffsets[0],
+ aTextOffsets[1]);
+ };
+
+ this.getID = function setVCRangeInvoker_getID()
+ {
+ return "Set offset in " + prettyName(aTextAccessible) +
+ " to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")";
+ };
+
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange", true)
+ ];
+}
+
+/**
+ * Move the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc,
+ aIsFromUserInput)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ if (aPivotMoveMethod && aRule) {
+ var moved = false;
+ switch (aPivotMoveMethod) {
+ case 'moveFirst':
+ case 'moveLast':
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput);
+ break;
+ case 'moveNext':
+ case 'movePrevious':
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule,
+ aDocAcc.virtualCursor.position, false,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput);
+ break;
+ }
+ SimpleTest.is(!!moved, !!expectMove,
+ "moved pivot with " + aPivotMoveMethod +
+ " to " + aIdOrNameOrAcc);
+ } else {
+ aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc);
+ }
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod;
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod,
+ aIsFromUserInput === undefined ? !!aPivotMoveMethod : aIsFromUserInput)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+/**
+ * Move the pivot by text and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aBoundary [in] boundary constant
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets,
+ aIdOrNameOrAcc, aIsFromUserInput)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(aDocAcc.virtualCursor.position);
+ var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary,
+ aIsFromUserInput === undefined ? true : false);
+ SimpleTest.is(!!moved, !!expectMove,
+ "moved pivot by text with " + aPivotMoveMethod +
+ " to " + aIdOrNameOrAcc);
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod + " in " +
+ prettyName(aIdOrNameOrAcc) + ", " + boundaryToString(aBoundary) +
+ ", [" + aTextOffsets + "]";
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+
+/**
+ * Move the pivot to the position under the point.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aX [in] screen x coordinate
+ * @param aY [in] screen y coordinate
+ * @param aIgnoreNoMatch [in] don't unset position if no object was found at
+ * point.
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ */
+function moveVCCoordInvoker(aDocAcc, aX, aY, aIgnoreNoMatch,
+ aRule, aIdOrNameOrAcc)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ var moved = aDocAcc.virtualCursor.moveToPoint(aRule, aX, aY,
+ aIgnoreNoMatch);
+ SimpleTest.ok((expectMove && moved) || (!expectMove && !moved),
+ "moved pivot");
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc;
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint', true)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+/**
+ * Change the pivot modalRoot
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aModalRootAcc [in] accessible of the modal root, or null
+ * @param aExpectedResult [in] error result expected. 0 if expecting success
+ */
+function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult)
+{
+ this.invoke = function setModalRootInvoker_invoke()
+ {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.modalRoot = aModalRootAcc;
+ } catch (x) {
+ SimpleTest.ok(
+ x.result, "Unexpected exception when changing modal root: " + x);
+ errorResult = x.result;
+ }
+
+ SimpleTest.is(errorResult, aExpectedResult,
+ "Did not get expected result when changing modalRoot");
+ };
+
+ this.getID = function setModalRootInvoker_getID()
+ {
+ return "Set modalRoot to " + prettyName(aModalRootAcc);
+ };
+
+ 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 [in] event queue in which to push invoker sequence.
+ * @param aDocAcc [in] the managing document of the virtual cursor we are
+ * testing
+ * @param aRule [in] the traversal rule to use in the invokers
+ * @param aModalRoot [in] a modal root to use in this traversal sequence
+ * @param aSequence [in] a sequence of accessible names or element ids to expect
+ * with the given rule in the given document
+ */
+function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence)
+{
+ aDocAcc.virtualCursor.position = null;
+
+ // Add modal root (if any)
+ aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0));
+
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
+
+ for (var i = 1; i < aSequence.length; i++) {
+ var invoker =
+ new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]);
+ aQueue.push(invoker);
+ }
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ for (var i = aSequence.length-2; i >= 0; i--) {
+ var invoker =
+ new setVCPosInvoker(aDocAcc, "movePrevious", aRule, aSequence[i]);
+ aQueue.push(invoker);
+ }
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveLast", aRule,
+ aSequence[aSequence.length - 1]));
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ // set isFromUserInput to false, just to test..
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0], false));
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ // Remove modal root (if any).
+ aQueue.push(new setModalRootInvoker(aDocAcc, null, 0));
+}
+
+/**
+ * A checker for removing an accessible while the virtual cursor is on it.
+ */
+function removeVCPositionChecker(aDocAcc, aHiddenParentAcc)
+{
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc);
+
+ this.check = function removeVCPositionChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.moveNext(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult, NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
+ };
+}
+
+/**
+ * Put the virtual cursor's position on an object, and then remove it.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPosNode [in] DOM node to hide after virtual cursor's position is
+ * set to it.
+ */
+function removeVCPositionInvoker(aDocAcc, aPosNode)
+{
+ this.accessible = getAccessible(aPosNode);
+ this.invoke = function removeVCPositionInvoker_invoke()
+ {
+ aDocAcc.virtualCursor.position = this.accessible;
+ aPosNode.parentNode.removeChild(aPosNode);
+ };
+
+ this.getID = function removeVCPositionInvoker_getID()
+ {
+ return "Bring virtual cursor to accessible, and remove its DOM node.";
+ };
+
+ this.eventSeq = [
+ new removeVCPositionChecker(aDocAcc, this.accessible.parent)
+ ];
+}
+
+/**
+ * A checker for removing the pivot root and then calling moveFirst, and
+ * checking that an exception is thrown.
+ */
+function removeVCRootChecker(aPivot)
+{
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent);
+
+ this.check = function removeVCRootChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aPivot.moveLast(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult, NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
+ };
+}
+
+/**
+ * Create a pivot, remove its root, and perform an operation where the root is
+ * needed.
+ *
+ * @param aRootNode [in] DOM node of which accessible will be the root of the
+ * pivot. Should have more than one child.
+ */
+function removeVCRootInvoker(aRootNode)
+{
+ this.pivot = gAccService.createAccessiblePivot(getAccessible(aRootNode));
+ this.invoke = function removeVCRootInvoker_invoke()
+ {
+ this.pivot.position = this.pivot.root.firstChild;
+ aRootNode.parentNode.removeChild(aRootNode);
+ };
+
+ this.getID = function removeVCRootInvoker_getID()
+ {
+ return "Remove root of pivot from tree.";
+ };
+
+ this.eventSeq = [
+ new removeVCRootChecker(this.pivot)
+ ];
+}
+
+/**
+ * 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");
+}