|
1 <html> |
|
2 |
|
3 <head> |
|
4 <title>Accessible mutation events testing</title> |
|
5 |
|
6 <link rel="stylesheet" type="text/css" |
|
7 href="chrome://mochikit/content/tests/SimpleTest/test.css" /> |
|
8 |
|
9 <style> |
|
10 div.displayNone a { display:none; } |
|
11 div.visibilityHidden a { visibility:hidden; } |
|
12 </style> |
|
13 |
|
14 <script type="application/javascript" |
|
15 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> |
|
16 <script type="application/javascript" |
|
17 src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> |
|
18 |
|
19 <script type="application/javascript" |
|
20 src="../common.js"></script> |
|
21 <script type="application/javascript" |
|
22 src="../events.js"></script> |
|
23 |
|
24 <script type="application/javascript"> |
|
25 /** |
|
26 * Invokers. |
|
27 */ |
|
28 var kNoEvents = 0; |
|
29 |
|
30 var kShowEvent = 1; |
|
31 var kHideEvent = 2; |
|
32 var kReorderEvent = 4; |
|
33 var kShowEvents = kShowEvent | kReorderEvent; |
|
34 var kHideEvents = kHideEvent | kReorderEvent; |
|
35 var kHideAndShowEvents = kHideEvents | kShowEvent; |
|
36 |
|
37 /** |
|
38 * Base class to test mutation a11y events. |
|
39 * |
|
40 * @param aNodeOrID [in] node invoker's action is executed for |
|
41 * @param aEventTypes [in] events to register (see constants above) |
|
42 * @param aDoNotExpectEvents [in] boolean indicates if events are expected |
|
43 */ |
|
44 function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) |
|
45 { |
|
46 // Interface |
|
47 this.DOMNode = getNode(aNodeOrID); |
|
48 this.doNotExpectEvents = aDoNotExpectEvents; |
|
49 this.eventSeq = []; |
|
50 this.unexpectedEventSeq = []; |
|
51 |
|
52 /** |
|
53 * Change default target (aNodeOrID) registered for the given event type. |
|
54 */ |
|
55 this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) |
|
56 { |
|
57 var type = this.getA11yEventType(aEventType); |
|
58 for (var idx = 0; idx < this.getEventSeq().length; idx++) { |
|
59 if (this.getEventSeq()[idx].type == type) { |
|
60 this.getEventSeq()[idx].target = aTarget; |
|
61 return idx; |
|
62 } |
|
63 } |
|
64 return -1; |
|
65 } |
|
66 |
|
67 /** |
|
68 * Replace the default target currently registered for a given event type |
|
69 * with the nodes in the targets array. |
|
70 */ |
|
71 this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) { |
|
72 var targetIdx = this.setTarget(aEventType, aTargets[0]); |
|
73 |
|
74 var type = this.getA11yEventType(aEventType); |
|
75 for (var i = 1; i < aTargets.length; i++) { |
|
76 var checker = new invokerChecker(type, aTargets[i]); |
|
77 this.getEventSeq().splice(++targetIdx, 0, checker); |
|
78 } |
|
79 } |
|
80 |
|
81 // Implementation |
|
82 this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) |
|
83 { |
|
84 if (aEventType == kReorderEvent) |
|
85 return nsIAccessibleEvent.EVENT_REORDER; |
|
86 |
|
87 if (aEventType == kHideEvent) |
|
88 return nsIAccessibleEvent.EVENT_HIDE; |
|
89 |
|
90 if (aEventType == kShowEvent) |
|
91 return nsIAccessibleEvent.EVENT_SHOW; |
|
92 } |
|
93 |
|
94 this.getEventSeq = function mutateA11yTree_getEventSeq() |
|
95 { |
|
96 return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq; |
|
97 } |
|
98 |
|
99 if (aEventTypes & kHideEvent) { |
|
100 var checker = new invokerChecker(this.getA11yEventType(kHideEvent), |
|
101 this.DOMNode); |
|
102 this.getEventSeq().push(checker); |
|
103 } |
|
104 |
|
105 if (aEventTypes & kShowEvent) { |
|
106 var checker = new invokerChecker(this.getA11yEventType(kShowEvent), |
|
107 this.DOMNode); |
|
108 this.getEventSeq().push(checker); |
|
109 } |
|
110 |
|
111 if (aEventTypes & kReorderEvent) { |
|
112 var checker = new invokerChecker(this.getA11yEventType(kReorderEvent), |
|
113 this.DOMNode.parentNode); |
|
114 this.getEventSeq().push(checker); |
|
115 } |
|
116 } |
|
117 |
|
118 /** |
|
119 * Change CSS style for the given node. |
|
120 */ |
|
121 function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) |
|
122 { |
|
123 this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); |
|
124 |
|
125 this.invoke = function changeStyle_invoke() |
|
126 { |
|
127 this.DOMNode.style[aProp] = aValue; |
|
128 } |
|
129 |
|
130 this.getID = function changeStyle_getID() |
|
131 { |
|
132 return aNodeOrID + " change style " + aProp + " on value " + aValue; |
|
133 } |
|
134 } |
|
135 |
|
136 /** |
|
137 * Change class name for the given node. |
|
138 */ |
|
139 function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) |
|
140 { |
|
141 this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); |
|
142 |
|
143 this.invoke = function changeClass_invoke() |
|
144 { |
|
145 this.parentDOMNode.className = aClassName; |
|
146 } |
|
147 |
|
148 this.getID = function changeClass_getID() |
|
149 { |
|
150 return aNodeOrID + " change class " + aClassName; |
|
151 } |
|
152 |
|
153 this.parentDOMNode = getNode(aParentNodeOrID); |
|
154 } |
|
155 |
|
156 /** |
|
157 * Clone the node and append it to its parent. |
|
158 */ |
|
159 function cloneAndAppendToDOM(aNodeOrID, aEventTypes, |
|
160 aTargetsFunc, aReorderTargetFunc) |
|
161 { |
|
162 var eventTypes = aEventTypes || kShowEvents; |
|
163 var doNotExpectEvents = (aEventTypes == kNoEvents); |
|
164 |
|
165 this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, |
|
166 doNotExpectEvents); |
|
167 |
|
168 this.invoke = function cloneAndAppendToDOM_invoke() |
|
169 { |
|
170 var newElm = this.DOMNode.cloneNode(true); |
|
171 newElm.removeAttribute('id'); |
|
172 |
|
173 var targets = aTargetsFunc ? |
|
174 aTargetsFunc.call(null, newElm) : [newElm]; |
|
175 this.setTargets(kShowEvent, targets); |
|
176 |
|
177 if (aReorderTargetFunc) { |
|
178 var reorderTarget = aReorderTargetFunc.call(null, this.DOMNode); |
|
179 this.setTarget(kReorderEvent, reorderTarget); |
|
180 } |
|
181 |
|
182 this.DOMNode.parentNode.appendChild(newElm); |
|
183 } |
|
184 |
|
185 this.getID = function cloneAndAppendToDOM_getID() |
|
186 { |
|
187 return aNodeOrID + " clone and append to DOM."; |
|
188 } |
|
189 } |
|
190 |
|
191 /** |
|
192 * Removes the node from DOM. |
|
193 */ |
|
194 function removeFromDOM(aNodeOrID, aEventTypes, |
|
195 aTargetsFunc, aReorderTargetFunc) |
|
196 { |
|
197 var eventTypes = aEventTypes || kHideEvents; |
|
198 var doNotExpectEvents = (aEventTypes == kNoEvents); |
|
199 |
|
200 this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, |
|
201 doNotExpectEvents); |
|
202 |
|
203 this.invoke = function removeFromDOM_invoke() |
|
204 { |
|
205 this.DOMNode.parentNode.removeChild(this.DOMNode); |
|
206 } |
|
207 |
|
208 this.getID = function removeFromDOM_getID() |
|
209 { |
|
210 return prettyName(aNodeOrID) + " remove from DOM."; |
|
211 } |
|
212 |
|
213 if (aTargetsFunc && (eventTypes & kHideEvent)) |
|
214 this.setTargets(kHideEvent, aTargetsFunc.call(null, this.DOMNode)); |
|
215 |
|
216 if (aReorderTargetFunc && (eventTypes & kReorderEvent)) |
|
217 this.setTarget(kReorderEvent, |
|
218 aReorderTargetFunc.call(null, this.DOMNode)); |
|
219 } |
|
220 |
|
221 /** |
|
222 * Clone the node and replace the original node by cloned one. |
|
223 */ |
|
224 function cloneAndReplaceInDOM(aNodeOrID) |
|
225 { |
|
226 this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents, |
|
227 false); |
|
228 |
|
229 this.invoke = function cloneAndReplaceInDOM_invoke() |
|
230 { |
|
231 this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode); |
|
232 } |
|
233 |
|
234 this.getID = function cloneAndReplaceInDOM_getID() |
|
235 { |
|
236 return aNodeOrID + " clone and replace in DOM."; |
|
237 } |
|
238 |
|
239 this.newElm = this.DOMNode.cloneNode(true); |
|
240 this.newElm.removeAttribute('id'); |
|
241 this.setTarget(kShowEvent, this.newElm); |
|
242 } |
|
243 |
|
244 /** |
|
245 * Trigger content insertion (flush layout), removal and insertion of |
|
246 * the same element for the same parent. |
|
247 */ |
|
248 function test1(aContainerID) |
|
249 { |
|
250 this.divNode = document.createElement("div"); |
|
251 this.divNode.setAttribute("id", "div-test1"); |
|
252 this.containerNode = getNode(aContainerID); |
|
253 |
|
254 this.eventSeq = [ |
|
255 new invokerChecker(EVENT_SHOW, this.divNode), |
|
256 new invokerChecker(EVENT_REORDER, this.containerNode) |
|
257 ]; |
|
258 |
|
259 this.invoke = function test1_invoke() |
|
260 { |
|
261 this.containerNode.appendChild(this.divNode); |
|
262 getComputedStyle(this.divNode, "").color; |
|
263 this.containerNode.removeChild(this.divNode); |
|
264 this.containerNode.appendChild(this.divNode); |
|
265 } |
|
266 |
|
267 this.getID = function test1_getID() |
|
268 { |
|
269 return "fuzzy test #1: content insertion (flush layout), removal and" + |
|
270 "reinsertion"; |
|
271 } |
|
272 } |
|
273 |
|
274 /** |
|
275 * Trigger content insertion (flush layout), removal and insertion of |
|
276 * the same element for the different parents. |
|
277 */ |
|
278 function test2(aContainerID, aTmpContainerID) |
|
279 { |
|
280 this.divNode = document.createElement("div"); |
|
281 this.divNode.setAttribute("id", "div-test2"); |
|
282 this.containerNode = getNode(aContainerID); |
|
283 this.tmpContainerNode = getNode(aTmpContainerID); |
|
284 this.container = getAccessible(this.containerNode); |
|
285 this.tmpContainer = getAccessible(this.tmpContainerNode); |
|
286 |
|
287 this.eventSeq = [ |
|
288 new invokerChecker(EVENT_SHOW, this.divNode), |
|
289 new invokerChecker(EVENT_REORDER, this.containerNode) |
|
290 ]; |
|
291 |
|
292 this.unexpectedEventSeq = [ |
|
293 new invokerChecker(EVENT_REORDER, this.tmpContainerNode) |
|
294 ]; |
|
295 |
|
296 this.invoke = function test2_invoke() |
|
297 { |
|
298 this.tmpContainerNode.appendChild(this.divNode); |
|
299 getComputedStyle(this.divNode, "").color; |
|
300 this.tmpContainerNode.removeChild(this.divNode); |
|
301 this.containerNode.appendChild(this.divNode); |
|
302 } |
|
303 |
|
304 this.getID = function test2_getID() |
|
305 { |
|
306 return "fuzzy test #2: content insertion (flush layout), removal and" + |
|
307 "reinsertion under another container"; |
|
308 } |
|
309 } |
|
310 |
|
311 /** |
|
312 * Content insertion (flush layout) and then removal (nothing was changed). |
|
313 */ |
|
314 function test3(aContainerID) |
|
315 { |
|
316 this.divNode = document.createElement("div"); |
|
317 this.divNode.setAttribute("id", "div-test3"); |
|
318 this.containerNode = getNode(aContainerID); |
|
319 |
|
320 this.unexpectedEventSeq = [ |
|
321 new invokerChecker(EVENT_SHOW, this.divNode), |
|
322 new invokerChecker(EVENT_HIDE, this.divNode), |
|
323 new invokerChecker(EVENT_REORDER, this.containerNode) |
|
324 ]; |
|
325 |
|
326 this.invoke = function test3_invoke() |
|
327 { |
|
328 this.containerNode.appendChild(this.divNode); |
|
329 getComputedStyle(this.divNode, "").color; |
|
330 this.containerNode.removeChild(this.divNode); |
|
331 } |
|
332 |
|
333 this.getID = function test3_getID() |
|
334 { |
|
335 return "fuzzy test #3: content insertion (flush layout) and removal"; |
|
336 } |
|
337 } |
|
338 |
|
339 /** |
|
340 * Target getters. |
|
341 */ |
|
342 function getFirstChild(aNode) |
|
343 { |
|
344 return [aNode.firstChild]; |
|
345 } |
|
346 |
|
347 function getNEnsureFirstChild(aNode) |
|
348 { |
|
349 var node = aNode.firstChild; |
|
350 getAccessible(node); |
|
351 return [node]; |
|
352 } |
|
353 |
|
354 function getNEnsureChildren(aNode) |
|
355 { |
|
356 var children = []; |
|
357 var node = aNode.firstChild; |
|
358 do { |
|
359 children.push(node); |
|
360 getAccessible(node); |
|
361 node = node.nextSibling; |
|
362 } while (node); |
|
363 |
|
364 return children; |
|
365 } |
|
366 |
|
367 function getParent(aNode) |
|
368 { |
|
369 return aNode.parentNode; |
|
370 } |
|
371 |
|
372 /** |
|
373 * Do tests. |
|
374 */ |
|
375 var gQueue = null; |
|
376 //gA11yEventDumpToConsole = true; // debug stuff |
|
377 |
|
378 function doTests() |
|
379 { |
|
380 gQueue = new eventQueue(); |
|
381 |
|
382 // Show/hide events by changing of display style of accessible DOM node |
|
383 // from 'inline' to 'none', 'none' to 'inline'. |
|
384 var id = "link1"; |
|
385 getAccessible(id); // ensure accessible is created |
|
386 gQueue.push(new changeStyle(id, "display", "none", kHideEvents)); |
|
387 gQueue.push(new changeStyle(id, "display", "inline", kShowEvents)); |
|
388 |
|
389 // Show/hide events by changing of visibility style of accessible DOM node |
|
390 // from 'visible' to 'hidden', 'hidden' to 'visible'. |
|
391 var id = "link2"; |
|
392 getAccessible(id); |
|
393 gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents)); |
|
394 gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); |
|
395 |
|
396 // Show/hide events by changing of display style of accessible DOM node |
|
397 // from 'inline' to 'block', 'block' to 'inline'. |
|
398 var id = "link3"; |
|
399 getAccessible(id); // ensure accessible is created |
|
400 gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents)); |
|
401 gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents)); |
|
402 |
|
403 // Show/hide events by changing of visibility style of accessible DOM node |
|
404 // from 'collapse' to 'visible', 'visible' to 'collapse'. |
|
405 var id = "link4"; |
|
406 gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); |
|
407 gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents)); |
|
408 |
|
409 // Show/hide events by adding new accessible DOM node and removing old one. |
|
410 var id = "link5"; |
|
411 gQueue.push(new cloneAndAppendToDOM(id)); |
|
412 gQueue.push(new removeFromDOM(id)); |
|
413 |
|
414 // No show/hide events by adding new not accessible DOM node and removing |
|
415 // old one, no reorder event for their parent. |
|
416 var id = "child1"; |
|
417 gQueue.push(new cloneAndAppendToDOM(id, kNoEvents)); |
|
418 gQueue.push(new removeFromDOM(id, kNoEvents)); |
|
419 |
|
420 // Show/hide events by adding new accessible DOM node and removing |
|
421 // old one, there is reorder event for their parent. |
|
422 var id = "child2"; |
|
423 gQueue.push(new cloneAndAppendToDOM(id)); |
|
424 gQueue.push(new removeFromDOM(id)); |
|
425 |
|
426 // Show/hide events by adding new DOM node containing accessible DOM and |
|
427 // removing old one, there is reorder event for their parent. |
|
428 var id = "child3"; |
|
429 gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild, |
|
430 getParent)); |
|
431 |
|
432 // Hide event for accessible child of unaccessible removed DOM node and |
|
433 // reorder event for its parent. |
|
434 gQueue.push(new removeFromDOM(id, kHideEvents, |
|
435 getNEnsureFirstChild, getParent)); |
|
436 |
|
437 // Hide events for accessible children of unaccessible removed DOM node |
|
438 // and reorder event for its parent. |
|
439 gQueue.push(new removeFromDOM("child4", kHideEvents, |
|
440 getNEnsureChildren, getParent)); |
|
441 |
|
442 // Show/hide events by creating new accessible DOM node and replacing |
|
443 // old one. |
|
444 getAccessible("link6"); // ensure accessible is created |
|
445 gQueue.push(new cloneAndReplaceInDOM("link6")); |
|
446 |
|
447 // Show/hide events by changing class name on the parent node. |
|
448 gQueue.push(new changeClass("container2", "link7", "", kShowEvents)); |
|
449 gQueue.push(new changeClass("container2", "link7", "displayNone", |
|
450 kHideEvents)); |
|
451 |
|
452 gQueue.push(new changeClass("container3", "link8", "", kShowEvents)); |
|
453 gQueue.push(new changeClass("container3", "link8", "visibilityHidden", |
|
454 kHideEvents)); |
|
455 |
|
456 gQueue.push(new test1("testContainer")); |
|
457 gQueue.push(new test2("testContainer", "testContainer2")); |
|
458 gQueue.push(new test2("testContainer", "testNestedContainer")); |
|
459 gQueue.push(new test3("testContainer")); |
|
460 |
|
461 gQueue.invoke(); // Will call SimpleTest.finish(); |
|
462 } |
|
463 |
|
464 SimpleTest.waitForExplicitFinish(); |
|
465 addA11yLoadEvent(doTests); |
|
466 </script> |
|
467 </head> |
|
468 |
|
469 <body> |
|
470 |
|
471 <a target="_blank" |
|
472 href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985" |
|
473 title=" turn the test from bug 354745 into mochitest"> |
|
474 Mozilla Bug 469985</a> |
|
475 <a target="_blank" |
|
476 href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662" |
|
477 title="no reorder event when html:link display property is changed from 'none' to 'inline'"> |
|
478 Mozilla Bug 472662</a> |
|
479 <a target="_blank" |
|
480 title="Rework accessible tree update code" |
|
481 href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> |
|
482 Mozilla Bug 570275</a> |
|
483 <a target="_blank" |
|
484 title="Develop a way to handle visibility style" |
|
485 href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125"> |
|
486 Mozilla Bug 606125</a> |
|
487 <a target="_blank" |
|
488 title="Update accessible tree on content insertion after layout" |
|
489 href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015"> |
|
490 Mozilla Bug 498015</a> |
|
491 |
|
492 <p id="display"></p> |
|
493 <div id="content" style="display: none"></div> |
|
494 <pre id="test"> |
|
495 </pre> |
|
496 <div id="eventdump"></div> |
|
497 |
|
498 <div id="testContainer"> |
|
499 <a id="link1" href="http://www.google.com">Link #1</a> |
|
500 <a id="link2" href="http://www.google.com">Link #2</a> |
|
501 <a id="link3" href="http://www.google.com">Link #3</a> |
|
502 <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a> |
|
503 <a id="link5" href="http://www.google.com">Link #5</a> |
|
504 |
|
505 <div id="container" role="list"> |
|
506 <span id="child1"></span> |
|
507 <span id="child2" role="listitem"></span> |
|
508 <span id="child3"><span role="listitem"></span></span> |
|
509 <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span> |
|
510 </div> |
|
511 |
|
512 <a id="link6" href="http://www.google.com">Link #6</a> |
|
513 |
|
514 <div id="container2" class="displayNone"><a id="link7">Link #7</a></div> |
|
515 <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div> |
|
516 <div id="testNestedContainer"></div> |
|
517 </div> |
|
518 <div id="testContainer2"></div> |
|
519 </body> |
|
520 </html> |