accessible/tests/mochitest/pivot.js

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:117034140f95
1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
2
3 ////////////////////////////////////////////////////////////////////////////////
4 // Constants
5
6 const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
7 const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN;
8 const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT;
9 const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
10 const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
11 const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
12 const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY;
13 const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY;
14
15 const NS_ERROR_NOT_IN_TREE = 0x80780026;
16 const NS_ERROR_INVALID_ARG = 0x80070057;
17
18 ////////////////////////////////////////////////////////////////////////////////
19 // Traversal rules
20
21 /**
22 * Rule object to traverse all focusable nodes and text nodes.
23 */
24 var HeadersTraversalRule =
25 {
26 getMatchRoles: function(aRules)
27 {
28 aRules.value = [ROLE_HEADING];
29 return aRules.value.length;
30 },
31
32 preFilter: PREFILTER_INVISIBLE,
33
34 match: function(aAccessible)
35 {
36 return FILTER_MATCH;
37 },
38
39 QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
40 }
41
42 /**
43 * Traversal rule for all focusable nodes or leafs.
44 */
45 var ObjectTraversalRule =
46 {
47 getMatchRoles: function(aRules)
48 {
49 aRules.value = [];
50 return 0;
51 },
52
53 preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT,
54
55 match: function(aAccessible)
56 {
57 var rv = FILTER_IGNORE;
58 var role = aAccessible.role;
59 if (hasState(aAccessible, STATE_FOCUSABLE) &&
60 (role != ROLE_DOCUMENT && role != ROLE_INTERNAL_FRAME))
61 rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH;
62 else if (aAccessible.childCount == 0 &&
63 role != ROLE_STATICTEXT && aAccessible.name.trim())
64 rv = FILTER_MATCH;
65
66 return rv;
67 },
68
69 QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
70 };
71
72 ////////////////////////////////////////////////////////////////////////////////
73 // Virtual state invokers and checkers
74
75 /**
76 * A checker for virtual cursor changed events.
77 */
78 function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod)
79 {
80 this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
81
82 this.match = function VCChangedChecker_check(aEvent)
83 {
84 var event = null;
85 try {
86 event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
87 } catch (e) {
88 return false;
89 }
90
91 var expectedReason = VCChangedChecker.methodReasonMap[aPivotMoveMethod] ||
92 nsIAccessiblePivot.REASON_NONE;
93
94 return event.reason == expectedReason;
95 };
96
97 this.check = function VCChangedChecker_check(aEvent)
98 {
99 SimpleTest.info("VCChangedChecker_check");
100
101 var event = null;
102 try {
103 event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
104 } catch (e) {
105 SimpleTest.ok(false, "Does not support correct interface: " + e);
106 }
107
108 var position = aDocAcc.virtualCursor.position;
109 var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc;
110 var nameMatches = position && position.name == aIdOrNameOrAcc;
111 var accMatches = position == aIdOrNameOrAcc;
112
113 SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches",
114 "expecting " + aIdOrNameOrAcc + ", got '" +
115 prettyName(position));
116
117 if (aTextOffsets) {
118 SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0],
119 "wrong start offset");
120 SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1],
121 "wrong end offset");
122 }
123
124 var prevPosAndOffset = VCChangedChecker.
125 getPreviousPosAndOffset(aDocAcc.virtualCursor);
126
127 if (prevPosAndOffset) {
128 SimpleTest.is(event.oldAccessible, prevPosAndOffset.position,
129 "previous position does not match");
130 SimpleTest.is(event.oldStartOffset, prevPosAndOffset.startOffset,
131 "previous start offset does not match");
132 SimpleTest.is(event.oldEndOffset, prevPosAndOffset.endOffset,
133 "previous end offset does not match");
134 }
135 };
136 }
137
138 VCChangedChecker.prevPosAndOffset = {};
139
140 VCChangedChecker.storePreviousPosAndOffset =
141 function storePreviousPosAndOffset(aPivot)
142 {
143 VCChangedChecker.prevPosAndOffset[aPivot] =
144 {position: aPivot.position,
145 startOffset: aPivot.startOffset,
146 endOffset: aPivot.endOffset};
147 };
148
149 VCChangedChecker.getPreviousPosAndOffset =
150 function getPreviousPosAndOffset(aPivot)
151 {
152 return VCChangedChecker.prevPosAndOffset[aPivot];
153 };
154
155 VCChangedChecker.methodReasonMap = {
156 'moveNext': nsIAccessiblePivot.REASON_NEXT,
157 'movePrevious': nsIAccessiblePivot.REASON_PREV,
158 'moveFirst': nsIAccessiblePivot.REASON_FIRST,
159 'moveLast': nsIAccessiblePivot.REASON_LAST,
160 'setTextRange': nsIAccessiblePivot.REASON_TEXT,
161 'moveNextByText': nsIAccessiblePivot.REASON_TEXT,
162 'movePreviousByText': nsIAccessiblePivot.REASON_TEXT,
163 'moveToPoint': nsIAccessiblePivot.REASON_POINT
164 };
165
166 /**
167 * Set a text range in the pivot and wait for virtual cursor change event.
168 *
169 * @param aDocAcc [in] document that manages the virtual cursor
170 * @param aTextAccessible [in] accessible to set to virtual cursor's position
171 * @param aTextOffsets [in] start and end offsets of text range to set in
172 * virtual cursor.
173 */
174 function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets)
175 {
176 this.invoke = function virtualCursorChangedInvoker_invoke()
177 {
178 VCChangedChecker.
179 storePreviousPosAndOffset(aDocAcc.virtualCursor);
180 SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets);
181 aDocAcc.virtualCursor.setTextRange(aTextAccessible,
182 aTextOffsets[0],
183 aTextOffsets[1]);
184 };
185
186 this.getID = function setVCRangeInvoker_getID()
187 {
188 return "Set offset in " + prettyName(aTextAccessible) +
189 " to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")";
190 };
191
192 this.eventSeq = [
193 new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange")
194 ];
195 }
196
197 /**
198 * Move the pivot and wait for virtual cursor change event.
199 *
200 * @param aDocAcc [in] document that manages the virtual cursor
201 * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
202 * @param aRule [in] traversal rule object
203 * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
204 * virtual cursor to land on after performing move method.
205 * false if no move is expected.
206 */
207 function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc)
208 {
209 var expectMove = (aIdOrNameOrAcc != false);
210 this.invoke = function virtualCursorChangedInvoker_invoke()
211 {
212 VCChangedChecker.
213 storePreviousPosAndOffset(aDocAcc.virtualCursor);
214 if (aPivotMoveMethod && aRule) {
215 var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule);
216 SimpleTest.is(!!moved, !!expectMove,
217 "moved pivot with " + aPivotMoveMethod +
218 " to " + aIdOrNameOrAcc);
219 } else {
220 aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc);
221 }
222 };
223
224 this.getID = function setVCPosInvoker_getID()
225 {
226 return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod;
227 };
228
229 if (expectMove) {
230 this.eventSeq = [
231 new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod)
232 ];
233 } else {
234 this.eventSeq = [];
235 this.unexpectedEventSeq = [
236 new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
237 ];
238 }
239 }
240
241 /**
242 * Move the pivot by text and wait for virtual cursor change event.
243 *
244 * @param aDocAcc [in] document that manages the virtual cursor
245 * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
246 * @param aBoundary [in] boundary constant
247 * @param aTextOffsets [in] start and end offsets of text range to set in
248 * virtual cursor.
249 * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
250 * virtual cursor to land on after performing move method.
251 * false if no move is expected.
252 */
253 function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, aIdOrNameOrAcc)
254 {
255 var expectMove = (aIdOrNameOrAcc != false);
256 this.invoke = function virtualCursorChangedInvoker_invoke()
257 {
258 VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
259 SimpleTest.info(aDocAcc.virtualCursor.position);
260 var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary);
261 SimpleTest.is(!!moved, !!expectMove,
262 "moved pivot by text with " + aPivotMoveMethod +
263 " to " + aIdOrNameOrAcc);
264 };
265
266 this.getID = function setVCPosInvoker_getID()
267 {
268 return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod + " in " +
269 prettyName(aIdOrNameOrAcc) + ", " + boundaryToString(aBoundary) +
270 ", [" + aTextOffsets + "]";
271 };
272
273 if (expectMove) {
274 this.eventSeq = [
275 new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod)
276 ];
277 } else {
278 this.eventSeq = [];
279 this.unexpectedEventSeq = [
280 new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
281 ];
282 }
283 }
284
285
286 /**
287 * Move the pivot to the position under the point.
288 *
289 * @param aDocAcc [in] document that manages the virtual cursor
290 * @param aX [in] screen x coordinate
291 * @param aY [in] screen y coordinate
292 * @param aIgnoreNoMatch [in] don't unset position if no object was found at
293 * point.
294 * @param aRule [in] traversal rule object
295 * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
296 * virtual cursor to land on after performing move method.
297 * false if no move is expected.
298 */
299 function moveVCCoordInvoker(aDocAcc, aX, aY, aIgnoreNoMatch,
300 aRule, aIdOrNameOrAcc)
301 {
302 var expectMove = (aIdOrNameOrAcc != false);
303 this.invoke = function virtualCursorChangedInvoker_invoke()
304 {
305 VCChangedChecker.
306 storePreviousPosAndOffset(aDocAcc.virtualCursor);
307 var moved = aDocAcc.virtualCursor.moveToPoint(aRule, aX, aY,
308 aIgnoreNoMatch);
309 SimpleTest.ok((expectMove && moved) || (!expectMove && !moved),
310 "moved pivot");
311 };
312
313 this.getID = function setVCPosInvoker_getID()
314 {
315 return "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc;
316 };
317
318 if (expectMove) {
319 this.eventSeq = [
320 new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint')
321 ];
322 } else {
323 this.eventSeq = [];
324 this.unexpectedEventSeq = [
325 new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
326 ];
327 }
328 }
329
330 /**
331 * Change the pivot modalRoot
332 *
333 * @param aDocAcc [in] document that manages the virtual cursor
334 * @param aModalRootAcc [in] accessible of the modal root, or null
335 * @param aExpectedResult [in] error result expected. 0 if expecting success
336 */
337 function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult)
338 {
339 this.invoke = function setModalRootInvoker_invoke()
340 {
341 var errorResult = 0;
342 try {
343 aDocAcc.virtualCursor.modalRoot = aModalRootAcc;
344 } catch (x) {
345 SimpleTest.ok(
346 x.result, "Unexpected exception when changing modal root: " + x);
347 errorResult = x.result;
348 }
349
350 SimpleTest.is(errorResult, aExpectedResult,
351 "Did not get expected result when changing modalRoot");
352 };
353
354 this.getID = function setModalRootInvoker_getID()
355 {
356 return "Set modalRoot to " + prettyName(aModalRootAcc);
357 };
358
359 this.eventSeq = [];
360 this.unexpectedEventSeq = [
361 new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
362 ];
363 }
364
365 /**
366 * Add invokers to a queue to test a rule and an expected sequence of element ids
367 * or accessible names for that rule in the given document.
368 *
369 * @param aQueue [in] event queue in which to push invoker sequence.
370 * @param aDocAcc [in] the managing document of the virtual cursor we are
371 * testing
372 * @param aRule [in] the traversal rule to use in the invokers
373 * @param aModalRoot [in] a modal root to use in this traversal sequence
374 * @param aSequence [in] a sequence of accessible names or element ids to expect
375 * with the given rule in the given document
376 */
377 function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence)
378 {
379 aDocAcc.virtualCursor.position = null;
380
381 // Add modal root (if any)
382 aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0));
383
384 aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
385
386 for (var i = 1; i < aSequence.length; i++) {
387 var invoker =
388 new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]);
389 aQueue.push(invoker);
390 }
391
392 // No further more matches for given rule, expect no virtual cursor changes.
393 aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
394
395 for (var i = aSequence.length-2; i >= 0; i--) {
396 var invoker =
397 new setVCPosInvoker(aDocAcc, "movePrevious", aRule, aSequence[i]);
398 aQueue.push(invoker);
399 }
400
401 // No previous more matches for given rule, expect no virtual cursor changes.
402 aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
403
404 aQueue.push(new setVCPosInvoker(aDocAcc, "moveLast", aRule,
405 aSequence[aSequence.length - 1]));
406
407 // No further more matches for given rule, expect no virtual cursor changes.
408 aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
409
410 aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
411
412 // No previous more matches for given rule, expect no virtual cursor changes.
413 aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
414
415 // Remove modal root (if any).
416 aQueue.push(new setModalRootInvoker(aDocAcc, null, 0));
417 }
418
419 /**
420 * A checker for removing an accessible while the virtual cursor is on it.
421 */
422 function removeVCPositionChecker(aDocAcc, aHiddenParentAcc)
423 {
424 this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc);
425
426 this.check = function removeVCPositionChecker_check(aEvent) {
427 var errorResult = 0;
428 try {
429 aDocAcc.virtualCursor.moveNext(ObjectTraversalRule);
430 } catch (x) {
431 errorResult = x.result;
432 }
433 SimpleTest.is(
434 errorResult, NS_ERROR_NOT_IN_TREE,
435 "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
436 };
437 }
438
439 /**
440 * Put the virtual cursor's position on an object, and then remove it.
441 *
442 * @param aDocAcc [in] document that manages the virtual cursor
443 * @param aPosNode [in] DOM node to hide after virtual cursor's position is
444 * set to it.
445 */
446 function removeVCPositionInvoker(aDocAcc, aPosNode)
447 {
448 this.accessible = getAccessible(aPosNode);
449 this.invoke = function removeVCPositionInvoker_invoke()
450 {
451 aDocAcc.virtualCursor.position = this.accessible;
452 aPosNode.parentNode.removeChild(aPosNode);
453 };
454
455 this.getID = function removeVCPositionInvoker_getID()
456 {
457 return "Bring virtual cursor to accessible, and remove its DOM node.";
458 };
459
460 this.eventSeq = [
461 new removeVCPositionChecker(aDocAcc, this.accessible.parent)
462 ];
463 }
464
465 /**
466 * A checker for removing the pivot root and then calling moveFirst, and
467 * checking that an exception is thrown.
468 */
469 function removeVCRootChecker(aPivot)
470 {
471 this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent);
472
473 this.check = function removeVCRootChecker_check(aEvent) {
474 var errorResult = 0;
475 try {
476 aPivot.moveLast(ObjectTraversalRule);
477 } catch (x) {
478 errorResult = x.result;
479 }
480 SimpleTest.is(
481 errorResult, NS_ERROR_NOT_IN_TREE,
482 "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
483 };
484 }
485
486 /**
487 * Create a pivot, remove its root, and perform an operation where the root is
488 * needed.
489 *
490 * @param aRootNode [in] DOM node of which accessible will be the root of the
491 * pivot. Should have more than one child.
492 */
493 function removeVCRootInvoker(aRootNode)
494 {
495 this.pivot = gAccRetrieval.createAccessiblePivot(getAccessible(aRootNode));
496 this.invoke = function removeVCRootInvoker_invoke()
497 {
498 this.pivot.position = this.pivot.root.firstChild;
499 aRootNode.parentNode.removeChild(aRootNode);
500 };
501
502 this.getID = function removeVCRootInvoker_getID()
503 {
504 return "Remove root of pivot from tree.";
505 };
506
507 this.eventSeq = [
508 new removeVCRootChecker(this.pivot)
509 ];
510 }
511
512 /**
513 * A debug utility for writing proper sequences for queueTraversalSequence.
514 */
515 function dumpTraversalSequence(aPivot, aRule)
516 {
517 var sequence = [];
518 if (aPivot.moveFirst(aRule)) {
519 do {
520 sequence.push("'" + prettyName(aPivot.position) + "'");
521 } while (aPivot.moveNext(aRule))
522 }
523 SimpleTest.info("\n[" + sequence.join(", ") + "]\n");
524 }

mercurial