|
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 var currentTest; |
|
3 var gIsRefImageLoaded = false; |
|
4 const gShouldOutputDebugInfo = false; |
|
5 |
|
6 function pollForSuccess() |
|
7 { |
|
8 if (!currentTest.isTestFinished) { |
|
9 if (!currentTest.reusingReferenceImage || (currentTest.reusingReferenceImage |
|
10 && gRefImageLoaded)) { |
|
11 currentTest.checkImage(); |
|
12 } |
|
13 |
|
14 setTimeout(pollForSuccess, currentTest.pollFreq); |
|
15 } |
|
16 }; |
|
17 |
|
18 function referencePoller() |
|
19 { |
|
20 currentTest.takeReferenceSnapshot(); |
|
21 } |
|
22 |
|
23 function reuseImageCallback() |
|
24 { |
|
25 gIsRefImageLoaded = true; |
|
26 } |
|
27 |
|
28 function failTest() |
|
29 { |
|
30 if (currentTest.isTestFinished || currentTest.closeFunc) { |
|
31 return; |
|
32 } |
|
33 |
|
34 ok(false, "timing out after " + currentTest.timeout + "ms. " |
|
35 + "Animated image still doesn't look correct, after poll #" |
|
36 + currentTest.pollCounter); |
|
37 currentTest.wereFailures = true; |
|
38 |
|
39 if (currentTest.currentSnapshotDataURI) { |
|
40 currentTest.outputDebugInfo("Snapshot #" + currentTest.pollCounter, |
|
41 "snapNum" + currentTest.pollCounter, |
|
42 currentTest.currentSnapshotDataURI); |
|
43 } |
|
44 |
|
45 currentTest.enableDisplay(document.getElementById(currentTest.debugElementId)); |
|
46 |
|
47 currentTest.cleanUpAndFinish(); |
|
48 }; |
|
49 |
|
50 /** |
|
51 * Create a new AnimationTest object. |
|
52 * |
|
53 * @param pollFreq The amount of time (in ms) to wait between consecutive |
|
54 * snapshots if the reference image and the test image don't match. |
|
55 * @param timeout The total amount of time (in ms) to wait before declaring the |
|
56 * test as failed. |
|
57 * @param referenceElementId The id attribute of the reference image element, or |
|
58 * the source of the image to change to, once the reference snapshot has |
|
59 * been successfully taken. This latter option could be used if you don't |
|
60 * want the image to become invisible at any time during the test. |
|
61 * @param imageElementId The id attribute of the test image element. |
|
62 * @param debugElementId The id attribute of the div where links should be |
|
63 * appended if the test fails. |
|
64 * @param cleanId The id attribute of the div or element to use as the 'clean' |
|
65 * test. This element is only enabled when we are testing to verify that |
|
66 * the reference image has been loaded. It can be undefined. |
|
67 * @param srcAttr The location of the source of the image, for preloading. This |
|
68 * is usually not required, but it useful for preloading reference |
|
69 * images. |
|
70 * @param xulTest A boolean value indicating whether or not this is a XUL test |
|
71 * (uses hidden=true/false rather than display: none to hide/show |
|
72 * elements). |
|
73 * @param closeFunc A function that should be called when this test is finished. |
|
74 * If null, then cleanUpAndFinish() will be called. This can be used to |
|
75 * chain tests together, so they are all finished exactly once. |
|
76 * @returns {AnimationTest} |
|
77 */ |
|
78 function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId, |
|
79 debugElementId, cleanId, srcAttr, xulTest, closeFunc) |
|
80 { |
|
81 // We want to test the cold loading behavior, so clear cache in case an |
|
82 // earlier test got our image in there already. |
|
83 clearImageCache(); |
|
84 |
|
85 this.wereFailures = false; |
|
86 this.pollFreq = pollFreq; |
|
87 this.timeout = timeout; |
|
88 this.imageElementId = imageElementId; |
|
89 this.referenceElementId = referenceElementId; |
|
90 |
|
91 if (!document.getElementById(referenceElementId)) { |
|
92 // In this case, we're assuming the user passed in a string that |
|
93 // indicates the source of the image they want to change to, |
|
94 // after the reference image has been taken. |
|
95 this.reusingImageAsReference = true; |
|
96 } |
|
97 |
|
98 this.srcAttr = srcAttr; |
|
99 this.debugElementId = debugElementId; |
|
100 this.referenceSnapshot = ""; // value will be set in takeReferenceSnapshot() |
|
101 this.pollCounter = 0; |
|
102 this.isTestFinished = false; |
|
103 this.numRefsTaken = 0; |
|
104 this.blankWaitTime = 0; |
|
105 |
|
106 this.cleanId = cleanId ? cleanId : ''; |
|
107 this.xulTest = xulTest ? xulTest : ''; |
|
108 this.closeFunc = closeFunc ? closeFunc : ''; |
|
109 }; |
|
110 |
|
111 AnimationTest.prototype.preloadImage = function() |
|
112 { |
|
113 if (this.srcAttr) { |
|
114 this.myImage = new Image(); |
|
115 this.myImage.onload = function() { currentTest.continueTest(); }; |
|
116 this.myImage.src = this.srcAttr; |
|
117 } else { |
|
118 this.continueTest(); |
|
119 } |
|
120 }; |
|
121 |
|
122 AnimationTest.prototype.outputDebugInfo = function(message, id, dataUri) |
|
123 { |
|
124 if (!gShouldOutputDebugInfo) { |
|
125 return; |
|
126 } |
|
127 var debugElement = document.getElementById(this.debugElementId); |
|
128 var newDataUriElement = document.createElement("a"); |
|
129 newDataUriElement.setAttribute("id", id); |
|
130 newDataUriElement.setAttribute("href", dataUri); |
|
131 newDataUriElement.appendChild(document.createTextNode(message)); |
|
132 debugElement.appendChild(newDataUriElement); |
|
133 var brElement = document.createElement("br"); |
|
134 debugElement.appendChild(brElement); |
|
135 todo(false, "Debug (" + id + "): " + message + " " + dataUri); |
|
136 }; |
|
137 |
|
138 AnimationTest.prototype.isFinished = function() |
|
139 { |
|
140 return this.isTestFinished; |
|
141 }; |
|
142 |
|
143 AnimationTest.prototype.takeCleanSnapshot = function() |
|
144 { |
|
145 var cleanElement; |
|
146 if (this.cleanId) { |
|
147 cleanElement = document.getElementById(this.cleanId); |
|
148 } |
|
149 |
|
150 // Enable clean page comparison element |
|
151 if (cleanElement) { |
|
152 this.enableDisplay(cleanElement); |
|
153 } |
|
154 |
|
155 // Take a snapshot of the initial (clean) page |
|
156 this.cleanSnapshot = snapshotWindow(window, false); |
|
157 |
|
158 // Disable the clean page comparison element |
|
159 if (cleanElement) { |
|
160 this.disableDisplay(cleanElement); |
|
161 } |
|
162 |
|
163 var dataString1 = "Clean Snapshot"; |
|
164 this.outputDebugInfo(dataString1, 'cleanSnap', |
|
165 this.cleanSnapshot.toDataURL()); |
|
166 }; |
|
167 |
|
168 AnimationTest.prototype.takeBlankSnapshot = function() |
|
169 { |
|
170 // Take a snapshot of the initial (essentially blank) page |
|
171 this.blankSnapshot = snapshotWindow(window, false); |
|
172 |
|
173 var dataString1 = "Initial Blank Snapshot"; |
|
174 this.outputDebugInfo(dataString1, 'blank1Snap', |
|
175 this.blankSnapshot.toDataURL()); |
|
176 }; |
|
177 |
|
178 /** |
|
179 * Begin the AnimationTest. This will utilize the information provided in the |
|
180 * constructor to invoke a mochitest on animated images. It will automatically |
|
181 * fail if allowed to run past the timeout. This will attempt to preload an |
|
182 * image, if applicable, and then asynchronously call continueTest(), or if not |
|
183 * applicable, synchronously trigger a call to continueTest(). |
|
184 */ |
|
185 AnimationTest.prototype.beginTest = function() |
|
186 { |
|
187 SimpleTest.waitForExplicitFinish(); |
|
188 |
|
189 currentTest = this; |
|
190 this.preloadImage(); |
|
191 }; |
|
192 |
|
193 /** |
|
194 * This is the second part of the test. It is triggered (eventually) from |
|
195 * beginTest() either synchronously or asynchronously, as an image load |
|
196 * callback. |
|
197 */ |
|
198 AnimationTest.prototype.continueTest = function() |
|
199 { |
|
200 // In case something goes wrong, fail earlier than mochitest timeout, |
|
201 // and with more information. |
|
202 setTimeout(failTest, this.timeout); |
|
203 |
|
204 if (!this.reusingImageAsReference) { |
|
205 this.disableDisplay(document.getElementById(this.imageElementId)); |
|
206 } |
|
207 |
|
208 this.takeReferenceSnapshot(); |
|
209 this.setupPolledImage(); |
|
210 SimpleTest.executeSoon(pollForSuccess); |
|
211 }; |
|
212 |
|
213 AnimationTest.prototype.setupPolledImage = function () |
|
214 { |
|
215 // Make sure the image is visible |
|
216 if (!this.reusingImageAsReference) { |
|
217 this.enableDisplay(document.getElementById(this.imageElementId)); |
|
218 var currentSnapshot = snapshotWindow(window, false); |
|
219 var result = compareSnapshots(currentSnapshot, |
|
220 this.referenceSnapshot, true); |
|
221 |
|
222 this.currentSnapshotDataURI = currentSnapshot.toDataURL(); |
|
223 |
|
224 if (result[0]) { |
|
225 // SUCCESS! |
|
226 ok(true, "Animated image looks correct, at poll #" |
|
227 + this.pollCounter); |
|
228 |
|
229 this.cleanUpAndFinish(); |
|
230 } |
|
231 } else { |
|
232 if (!gIsRefImageLoaded) { |
|
233 this.myImage = new Image(); |
|
234 this.myImage.onload = reuseImageCallback; |
|
235 document.getElementById(this.imageElementId).setAttribute('src', |
|
236 this.referenceElementId); |
|
237 } |
|
238 } |
|
239 } |
|
240 |
|
241 AnimationTest.prototype.checkImage = function () |
|
242 { |
|
243 if (this.isTestFinished) { |
|
244 return; |
|
245 } |
|
246 |
|
247 this.pollCounter++; |
|
248 |
|
249 // We need this for some tests, because we need to force the |
|
250 // test image to be visible. |
|
251 if (!this.reusingImageAsReference) { |
|
252 this.enableDisplay(document.getElementById(this.imageElementId)); |
|
253 } |
|
254 |
|
255 var currentSnapshot = snapshotWindow(window, false); |
|
256 var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true); |
|
257 |
|
258 this.currentSnapshotDataURI = currentSnapshot.toDataURL(); |
|
259 |
|
260 if (result[0]) { |
|
261 // SUCCESS! |
|
262 ok(true, "Animated image looks correct, at poll #" |
|
263 + this.pollCounter); |
|
264 |
|
265 this.cleanUpAndFinish(); |
|
266 } |
|
267 }; |
|
268 |
|
269 AnimationTest.prototype.takeReferenceSnapshot = function () |
|
270 { |
|
271 this.numRefsTaken++; |
|
272 |
|
273 // Test to make sure the reference image doesn't match a clean snapshot |
|
274 if (!this.cleanSnapshot) { |
|
275 this.takeCleanSnapshot(); |
|
276 } |
|
277 |
|
278 // Used later to verify that the reference div disappeared |
|
279 if (!this.blankSnapshot) { |
|
280 this.takeBlankSnapshot(); |
|
281 } |
|
282 |
|
283 if (this.reusingImageAsReference) { |
|
284 // Show reference elem (which is actually our image), & take a snapshot |
|
285 var referenceElem = document.getElementById(this.imageElementId); |
|
286 this.enableDisplay(referenceElem); |
|
287 |
|
288 this.referenceSnapshot = snapshotWindow(window, false); |
|
289 |
|
290 var snapResult = compareSnapshots(this.cleanSnapshot, |
|
291 this.referenceSnapshot, false); |
|
292 if (!snapResult[0]) { |
|
293 if (this.blankWaitTime > 2000) { |
|
294 // if it took longer than two seconds to load the image, we probably |
|
295 // have a problem. |
|
296 this.wereFailures = true; |
|
297 ok(snapResult[0], |
|
298 "Reference snapshot shouldn't match clean (non-image) snapshot"); |
|
299 } else { |
|
300 this.blankWaitTime += currentTest.pollFreq; |
|
301 // let's wait a bit and see if it clears up |
|
302 setTimeout(referencePoller, currentTest.pollFreq); |
|
303 return; |
|
304 } |
|
305 } |
|
306 |
|
307 ok(snapResult[0], |
|
308 "Reference snapshot shouldn't match clean (non-image) snapshot"); |
|
309 |
|
310 var dataString = "Reference Snapshot #" + this.numRefsTaken; |
|
311 this.outputDebugInfo(dataString, 'refSnapId', |
|
312 this.referenceSnapshot.toDataURL()); |
|
313 } else { |
|
314 // Make sure the animation section is hidden |
|
315 this.disableDisplay(document.getElementById(this.imageElementId)); |
|
316 |
|
317 // Show reference div, & take a snapshot |
|
318 var referenceDiv = document.getElementById(this.referenceElementId); |
|
319 this.enableDisplay(referenceDiv); |
|
320 |
|
321 this.referenceSnapshot = snapshotWindow(window, false); |
|
322 var snapResult = compareSnapshots(this.cleanSnapshot, |
|
323 this.referenceSnapshot, false); |
|
324 if (!snapResult[0]) { |
|
325 if (this.blankWaitTime > 2000) { |
|
326 // if it took longer than two seconds to load the image, we probably |
|
327 // have a problem. |
|
328 this.wereFailures = true; |
|
329 ok(snapResult[0], |
|
330 "Reference snapshot shouldn't match clean (non-image) snapshot"); |
|
331 } else { |
|
332 this.blankWaitTime += 20; |
|
333 // let's wait a bit and see if it clears up |
|
334 setTimeout(referencePoller, 20); |
|
335 return; |
|
336 } |
|
337 } |
|
338 |
|
339 ok(snapResult[0], |
|
340 "Reference snapshot shouldn't match clean (non-image) snapshot"); |
|
341 |
|
342 var dataString = "Reference Snapshot #" + this.numRefsTaken; |
|
343 this.outputDebugInfo(dataString, 'refSnapId', |
|
344 this.referenceSnapshot.toDataURL()); |
|
345 |
|
346 // Re-hide reference div, and take another snapshot to be sure it's gone |
|
347 this.disableDisplay(referenceDiv); |
|
348 this.testBlankCameBack(); |
|
349 } |
|
350 }; |
|
351 |
|
352 AnimationTest.prototype.enableDisplay = function(element) |
|
353 { |
|
354 if (!element) { |
|
355 return; |
|
356 } |
|
357 |
|
358 if (!this.xulTest) { |
|
359 element.style.display = ''; |
|
360 } else { |
|
361 element.setAttribute('hidden', 'false'); |
|
362 } |
|
363 }; |
|
364 |
|
365 AnimationTest.prototype.disableDisplay = function(element) |
|
366 { |
|
367 if (!element) { |
|
368 return; |
|
369 } |
|
370 |
|
371 if (!this.xulTest) { |
|
372 element.style.display = 'none'; |
|
373 } else { |
|
374 element.setAttribute('hidden', 'true'); |
|
375 } |
|
376 }; |
|
377 |
|
378 AnimationTest.prototype.testBlankCameBack = function() |
|
379 { |
|
380 var blankSnapshot2 = snapshotWindow(window, false); |
|
381 var result = compareSnapshots(this.blankSnapshot, blankSnapshot2, true); |
|
382 ok(result[0], "Reference image should disappear when it becomes display:none"); |
|
383 |
|
384 if (!result[0]) { |
|
385 this.wereFailures = true; |
|
386 var dataString = "Second Blank Snapshot"; |
|
387 this.outputDebugInfo(dataString, 'blank2SnapId', result[2]); |
|
388 } |
|
389 }; |
|
390 |
|
391 AnimationTest.prototype.cleanUpAndFinish = function () |
|
392 { |
|
393 // On the off chance that failTest and checkImage are triggered |
|
394 // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish. |
|
395 if (this.isTestFinished) { |
|
396 return; |
|
397 } |
|
398 |
|
399 this.isTestFinished = true; |
|
400 |
|
401 // Call our closing function, if one exists |
|
402 if (this.closeFunc) { |
|
403 this.closeFunc(); |
|
404 return; |
|
405 } |
|
406 |
|
407 if (this.wereFailures) { |
|
408 document.getElementById(this.debugElementId).style.display = 'block'; |
|
409 } |
|
410 |
|
411 SimpleTest.finish(); |
|
412 document.getElementById(this.debugElementId).style.display = ""; |
|
413 }; |