michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: var currentTest; michael@0: var gIsRefImageLoaded = false; michael@0: const gShouldOutputDebugInfo = false; michael@0: michael@0: function pollForSuccess() michael@0: { michael@0: if (!currentTest.isTestFinished) { michael@0: if (!currentTest.reusingReferenceImage || (currentTest.reusingReferenceImage michael@0: && gRefImageLoaded)) { michael@0: currentTest.checkImage(); michael@0: } michael@0: michael@0: setTimeout(pollForSuccess, currentTest.pollFreq); michael@0: } michael@0: }; michael@0: michael@0: function referencePoller() michael@0: { michael@0: currentTest.takeReferenceSnapshot(); michael@0: } michael@0: michael@0: function reuseImageCallback() michael@0: { michael@0: gIsRefImageLoaded = true; michael@0: } michael@0: michael@0: function failTest() michael@0: { michael@0: if (currentTest.isTestFinished || currentTest.closeFunc) { michael@0: return; michael@0: } michael@0: michael@0: ok(false, "timing out after " + currentTest.timeout + "ms. " michael@0: + "Animated image still doesn't look correct, after poll #" michael@0: + currentTest.pollCounter); michael@0: currentTest.wereFailures = true; michael@0: michael@0: if (currentTest.currentSnapshotDataURI) { michael@0: currentTest.outputDebugInfo("Snapshot #" + currentTest.pollCounter, michael@0: "snapNum" + currentTest.pollCounter, michael@0: currentTest.currentSnapshotDataURI); michael@0: } michael@0: michael@0: currentTest.enableDisplay(document.getElementById(currentTest.debugElementId)); michael@0: michael@0: currentTest.cleanUpAndFinish(); michael@0: }; michael@0: michael@0: /** michael@0: * Create a new AnimationTest object. michael@0: * michael@0: * @param pollFreq The amount of time (in ms) to wait between consecutive michael@0: * snapshots if the reference image and the test image don't match. michael@0: * @param timeout The total amount of time (in ms) to wait before declaring the michael@0: * test as failed. michael@0: * @param referenceElementId The id attribute of the reference image element, or michael@0: * the source of the image to change to, once the reference snapshot has michael@0: * been successfully taken. This latter option could be used if you don't michael@0: * want the image to become invisible at any time during the test. michael@0: * @param imageElementId The id attribute of the test image element. michael@0: * @param debugElementId The id attribute of the div where links should be michael@0: * appended if the test fails. michael@0: * @param cleanId The id attribute of the div or element to use as the 'clean' michael@0: * test. This element is only enabled when we are testing to verify that michael@0: * the reference image has been loaded. It can be undefined. michael@0: * @param srcAttr The location of the source of the image, for preloading. This michael@0: * is usually not required, but it useful for preloading reference michael@0: * images. michael@0: * @param xulTest A boolean value indicating whether or not this is a XUL test michael@0: * (uses hidden=true/false rather than display: none to hide/show michael@0: * elements). michael@0: * @param closeFunc A function that should be called when this test is finished. michael@0: * If null, then cleanUpAndFinish() will be called. This can be used to michael@0: * chain tests together, so they are all finished exactly once. michael@0: * @returns {AnimationTest} michael@0: */ michael@0: function AnimationTest(pollFreq, timeout, referenceElementId, imageElementId, michael@0: debugElementId, cleanId, srcAttr, xulTest, closeFunc) michael@0: { michael@0: // We want to test the cold loading behavior, so clear cache in case an michael@0: // earlier test got our image in there already. michael@0: clearImageCache(); michael@0: michael@0: this.wereFailures = false; michael@0: this.pollFreq = pollFreq; michael@0: this.timeout = timeout; michael@0: this.imageElementId = imageElementId; michael@0: this.referenceElementId = referenceElementId; michael@0: michael@0: if (!document.getElementById(referenceElementId)) { michael@0: // In this case, we're assuming the user passed in a string that michael@0: // indicates the source of the image they want to change to, michael@0: // after the reference image has been taken. michael@0: this.reusingImageAsReference = true; michael@0: } michael@0: michael@0: this.srcAttr = srcAttr; michael@0: this.debugElementId = debugElementId; michael@0: this.referenceSnapshot = ""; // value will be set in takeReferenceSnapshot() michael@0: this.pollCounter = 0; michael@0: this.isTestFinished = false; michael@0: this.numRefsTaken = 0; michael@0: this.blankWaitTime = 0; michael@0: michael@0: this.cleanId = cleanId ? cleanId : ''; michael@0: this.xulTest = xulTest ? xulTest : ''; michael@0: this.closeFunc = closeFunc ? closeFunc : ''; michael@0: }; michael@0: michael@0: AnimationTest.prototype.preloadImage = function() michael@0: { michael@0: if (this.srcAttr) { michael@0: this.myImage = new Image(); michael@0: this.myImage.onload = function() { currentTest.continueTest(); }; michael@0: this.myImage.src = this.srcAttr; michael@0: } else { michael@0: this.continueTest(); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.outputDebugInfo = function(message, id, dataUri) michael@0: { michael@0: if (!gShouldOutputDebugInfo) { michael@0: return; michael@0: } michael@0: var debugElement = document.getElementById(this.debugElementId); michael@0: var newDataUriElement = document.createElement("a"); michael@0: newDataUriElement.setAttribute("id", id); michael@0: newDataUriElement.setAttribute("href", dataUri); michael@0: newDataUriElement.appendChild(document.createTextNode(message)); michael@0: debugElement.appendChild(newDataUriElement); michael@0: var brElement = document.createElement("br"); michael@0: debugElement.appendChild(brElement); michael@0: todo(false, "Debug (" + id + "): " + message + " " + dataUri); michael@0: }; michael@0: michael@0: AnimationTest.prototype.isFinished = function() michael@0: { michael@0: return this.isTestFinished; michael@0: }; michael@0: michael@0: AnimationTest.prototype.takeCleanSnapshot = function() michael@0: { michael@0: var cleanElement; michael@0: if (this.cleanId) { michael@0: cleanElement = document.getElementById(this.cleanId); michael@0: } michael@0: michael@0: // Enable clean page comparison element michael@0: if (cleanElement) { michael@0: this.enableDisplay(cleanElement); michael@0: } michael@0: michael@0: // Take a snapshot of the initial (clean) page michael@0: this.cleanSnapshot = snapshotWindow(window, false); michael@0: michael@0: // Disable the clean page comparison element michael@0: if (cleanElement) { michael@0: this.disableDisplay(cleanElement); michael@0: } michael@0: michael@0: var dataString1 = "Clean Snapshot"; michael@0: this.outputDebugInfo(dataString1, 'cleanSnap', michael@0: this.cleanSnapshot.toDataURL()); michael@0: }; michael@0: michael@0: AnimationTest.prototype.takeBlankSnapshot = function() michael@0: { michael@0: // Take a snapshot of the initial (essentially blank) page michael@0: this.blankSnapshot = snapshotWindow(window, false); michael@0: michael@0: var dataString1 = "Initial Blank Snapshot"; michael@0: this.outputDebugInfo(dataString1, 'blank1Snap', michael@0: this.blankSnapshot.toDataURL()); michael@0: }; michael@0: michael@0: /** michael@0: * Begin the AnimationTest. This will utilize the information provided in the michael@0: * constructor to invoke a mochitest on animated images. It will automatically michael@0: * fail if allowed to run past the timeout. This will attempt to preload an michael@0: * image, if applicable, and then asynchronously call continueTest(), or if not michael@0: * applicable, synchronously trigger a call to continueTest(). michael@0: */ michael@0: AnimationTest.prototype.beginTest = function() michael@0: { michael@0: SimpleTest.waitForExplicitFinish(); michael@0: michael@0: currentTest = this; michael@0: this.preloadImage(); michael@0: }; michael@0: michael@0: /** michael@0: * This is the second part of the test. It is triggered (eventually) from michael@0: * beginTest() either synchronously or asynchronously, as an image load michael@0: * callback. michael@0: */ michael@0: AnimationTest.prototype.continueTest = function() michael@0: { michael@0: // In case something goes wrong, fail earlier than mochitest timeout, michael@0: // and with more information. michael@0: setTimeout(failTest, this.timeout); michael@0: michael@0: if (!this.reusingImageAsReference) { michael@0: this.disableDisplay(document.getElementById(this.imageElementId)); michael@0: } michael@0: michael@0: this.takeReferenceSnapshot(); michael@0: this.setupPolledImage(); michael@0: SimpleTest.executeSoon(pollForSuccess); michael@0: }; michael@0: michael@0: AnimationTest.prototype.setupPolledImage = function () michael@0: { michael@0: // Make sure the image is visible michael@0: if (!this.reusingImageAsReference) { michael@0: this.enableDisplay(document.getElementById(this.imageElementId)); michael@0: var currentSnapshot = snapshotWindow(window, false); michael@0: var result = compareSnapshots(currentSnapshot, michael@0: this.referenceSnapshot, true); michael@0: michael@0: this.currentSnapshotDataURI = currentSnapshot.toDataURL(); michael@0: michael@0: if (result[0]) { michael@0: // SUCCESS! michael@0: ok(true, "Animated image looks correct, at poll #" michael@0: + this.pollCounter); michael@0: michael@0: this.cleanUpAndFinish(); michael@0: } michael@0: } else { michael@0: if (!gIsRefImageLoaded) { michael@0: this.myImage = new Image(); michael@0: this.myImage.onload = reuseImageCallback; michael@0: document.getElementById(this.imageElementId).setAttribute('src', michael@0: this.referenceElementId); michael@0: } michael@0: } michael@0: } michael@0: michael@0: AnimationTest.prototype.checkImage = function () michael@0: { michael@0: if (this.isTestFinished) { michael@0: return; michael@0: } michael@0: michael@0: this.pollCounter++; michael@0: michael@0: // We need this for some tests, because we need to force the michael@0: // test image to be visible. michael@0: if (!this.reusingImageAsReference) { michael@0: this.enableDisplay(document.getElementById(this.imageElementId)); michael@0: } michael@0: michael@0: var currentSnapshot = snapshotWindow(window, false); michael@0: var result = compareSnapshots(currentSnapshot, this.referenceSnapshot, true); michael@0: michael@0: this.currentSnapshotDataURI = currentSnapshot.toDataURL(); michael@0: michael@0: if (result[0]) { michael@0: // SUCCESS! michael@0: ok(true, "Animated image looks correct, at poll #" michael@0: + this.pollCounter); michael@0: michael@0: this.cleanUpAndFinish(); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.takeReferenceSnapshot = function () michael@0: { michael@0: this.numRefsTaken++; michael@0: michael@0: // Test to make sure the reference image doesn't match a clean snapshot michael@0: if (!this.cleanSnapshot) { michael@0: this.takeCleanSnapshot(); michael@0: } michael@0: michael@0: // Used later to verify that the reference div disappeared michael@0: if (!this.blankSnapshot) { michael@0: this.takeBlankSnapshot(); michael@0: } michael@0: michael@0: if (this.reusingImageAsReference) { michael@0: // Show reference elem (which is actually our image), & take a snapshot michael@0: var referenceElem = document.getElementById(this.imageElementId); michael@0: this.enableDisplay(referenceElem); michael@0: michael@0: this.referenceSnapshot = snapshotWindow(window, false); michael@0: michael@0: var snapResult = compareSnapshots(this.cleanSnapshot, michael@0: this.referenceSnapshot, false); michael@0: if (!snapResult[0]) { michael@0: if (this.blankWaitTime > 2000) { michael@0: // if it took longer than two seconds to load the image, we probably michael@0: // have a problem. michael@0: this.wereFailures = true; michael@0: ok(snapResult[0], michael@0: "Reference snapshot shouldn't match clean (non-image) snapshot"); michael@0: } else { michael@0: this.blankWaitTime += currentTest.pollFreq; michael@0: // let's wait a bit and see if it clears up michael@0: setTimeout(referencePoller, currentTest.pollFreq); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: ok(snapResult[0], michael@0: "Reference snapshot shouldn't match clean (non-image) snapshot"); michael@0: michael@0: var dataString = "Reference Snapshot #" + this.numRefsTaken; michael@0: this.outputDebugInfo(dataString, 'refSnapId', michael@0: this.referenceSnapshot.toDataURL()); michael@0: } else { michael@0: // Make sure the animation section is hidden michael@0: this.disableDisplay(document.getElementById(this.imageElementId)); michael@0: michael@0: // Show reference div, & take a snapshot michael@0: var referenceDiv = document.getElementById(this.referenceElementId); michael@0: this.enableDisplay(referenceDiv); michael@0: michael@0: this.referenceSnapshot = snapshotWindow(window, false); michael@0: var snapResult = compareSnapshots(this.cleanSnapshot, michael@0: this.referenceSnapshot, false); michael@0: if (!snapResult[0]) { michael@0: if (this.blankWaitTime > 2000) { michael@0: // if it took longer than two seconds to load the image, we probably michael@0: // have a problem. michael@0: this.wereFailures = true; michael@0: ok(snapResult[0], michael@0: "Reference snapshot shouldn't match clean (non-image) snapshot"); michael@0: } else { michael@0: this.blankWaitTime += 20; michael@0: // let's wait a bit and see if it clears up michael@0: setTimeout(referencePoller, 20); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: ok(snapResult[0], michael@0: "Reference snapshot shouldn't match clean (non-image) snapshot"); michael@0: michael@0: var dataString = "Reference Snapshot #" + this.numRefsTaken; michael@0: this.outputDebugInfo(dataString, 'refSnapId', michael@0: this.referenceSnapshot.toDataURL()); michael@0: michael@0: // Re-hide reference div, and take another snapshot to be sure it's gone michael@0: this.disableDisplay(referenceDiv); michael@0: this.testBlankCameBack(); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.enableDisplay = function(element) michael@0: { michael@0: if (!element) { michael@0: return; michael@0: } michael@0: michael@0: if (!this.xulTest) { michael@0: element.style.display = ''; michael@0: } else { michael@0: element.setAttribute('hidden', 'false'); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.disableDisplay = function(element) michael@0: { michael@0: if (!element) { michael@0: return; michael@0: } michael@0: michael@0: if (!this.xulTest) { michael@0: element.style.display = 'none'; michael@0: } else { michael@0: element.setAttribute('hidden', 'true'); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.testBlankCameBack = function() michael@0: { michael@0: var blankSnapshot2 = snapshotWindow(window, false); michael@0: var result = compareSnapshots(this.blankSnapshot, blankSnapshot2, true); michael@0: ok(result[0], "Reference image should disappear when it becomes display:none"); michael@0: michael@0: if (!result[0]) { michael@0: this.wereFailures = true; michael@0: var dataString = "Second Blank Snapshot"; michael@0: this.outputDebugInfo(dataString, 'blank2SnapId', result[2]); michael@0: } michael@0: }; michael@0: michael@0: AnimationTest.prototype.cleanUpAndFinish = function () michael@0: { michael@0: // On the off chance that failTest and checkImage are triggered michael@0: // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish. michael@0: if (this.isTestFinished) { michael@0: return; michael@0: } michael@0: michael@0: this.isTestFinished = true; michael@0: michael@0: // Call our closing function, if one exists michael@0: if (this.closeFunc) { michael@0: this.closeFunc(); michael@0: return; michael@0: } michael@0: michael@0: if (this.wereFailures) { michael@0: document.getElementById(this.debugElementId).style.display = 'block'; michael@0: } michael@0: michael@0: SimpleTest.finish(); michael@0: document.getElementById(this.debugElementId).style.display = ""; michael@0: };