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